gstack (Garry's Stack) is an all-in-one AI engineering toolkit built on Bun, providing headless browser automation, AI-powered design generation, PDF rendering, and 30+ Claude Code slash-command skills. This guide covers architecture, configuration, deployment, security, and developer internals for administrators and contributors working with gstack v1.10.1.0.
1. Architecture Overview
1.1 System Design
gstack is organized as a monorepo with four primary subsystems that share a common Bun runtime:
┌─────────────────────────────────────────────────────────────┐
│ gstack CLI │
│ browse/src/cli.ts → HTTP → browse/src/server.ts (Bun) │
│ ├── BrowserManager (Playwright)│
│ ├── Security Classifier │
│ ├── Activity Stream (SSE) │
│ ├── Token Registry (multi-agent)│
│ └── Sidebar Agent (Claude) │
├─────────────────────────────────────────────────────────────┤
│ design/src/cli.ts → OpenAI GPT-4o Image API │
│ make-pdf/src/cli.ts → browse CLI → Chromium PDF │
├─────────────────────────────────────────────────────────────┤
│ Skills System │
│ ├── SKILL.md.tmpl (templates with resolver placeholders) │
│ ├── scripts/resolvers/ (dynamic content generators) │
│ ├── scripts/gen-skill-docs.ts (multi-host doc generation) │
│ └── hosts/ (per-host config: path rewrites, frontmatter) │
├─────────────────────────────────────────────────────────────┤
│ Supabase Backend │
│ ├── telemetry-ingest (edge function) │
│ ├── community-pulse (edge function, cached) │
│ ├── update-check (edge function) │
│ └── Migrations (001-004): telemetry, RLS, upsert, attack │
├─────────────────────────────────────────────────────────────┤
│ Chrome Extension │
│ ├── background.js (health polling, command proxy, refs) │
│ ├── sidepanel.js (activity stream, chat) │
│ ├── content.js (indicator injection, inspector) │
│ └── inspector.js (element picking) │
└─────────────────────────────────────────────────────────────┘
browse is the centerpiece: a persistent HTTP server wrapping Playwright-driven Chromium. The CLI (browse/src/cli.ts) is a thin client that sends commands over HTTP to the long-running server (browse/src/server.ts). This architecture means the browser persists across individual command invocations, preserving page state, cookies, and sessions.
design is a standalone tool that calls the OpenAI GPT-4o Responses API for image generation, with iterative refinement, vision-based quality gates, and gallery management.
make-pdf orchestrates Markdown-to-PDF conversion by rendering HTML through the browse server's Chromium instance, using print-optimized CSS.
Skills are template-driven slash commands. Each skill is a .md.tmpl file with resolver placeholders (e.g., {{BROWSE_COMMAND_REFERENCE}}) that get expanded at generation time by TypeScript resolvers. The gen-skill-docs.ts script produces host-specific versions for each supported AI coding environment.
1.2 Process Model
The browse server uses Bun's HTTP server with a single-threaded event loop. Key architectural decisions:
- Persistent server: The browse server starts once and stays alive, with an idle timeout (
BROWSE_IDLE_TIMEOUT). CLI commands talk to it over localhost HTTP. - Token-based auth: The server generates a random token on startup, writes it to a state file, and all
/commandrequests requireBearer <token>. - Tab sessions: Each browser tab is managed by a
TabSessioninstance (browse/src/tab-session.ts) that tracks refs, frames, and loaded HTML independently. - Watchdog: The CLI can set
BROWSE_PARENT_PIDso the server auto-exits when the parent process dies (set to0to disable).
1.3 Technology Stack
| Technology | Version | Purpose |
|---|---|---|
| Bun | >=1.0.0 | Runtime, package manager, test runner, compiler |
| TypeScript | — | Primary language across all components |
| Playwright | ^1.58.2 | Browser automation (Chromium) |
| puppeteer-core | ^24.40.0 | Additional browser CDP APIs |
| @huggingface/transformers | ^4.1.0 | ML model loading for security classifier |
| @ngrok/ngrok | ^1.7.0 | Tunneling for remote browser access |
| marked | ^18.0.2 | Markdown parsing (make-pdf) |
| diff | ^7.0.0 | Text diffing |
| OpenAI GPT-4o | — | Design image generation + vision quality gate |
| @anthropic-ai/claude-agent-sdk | 0.2.117 | Agent SDK for sidebar agent (dev dependency) |
| @anthropic-ai/sdk | ^0.78.0 | Anthropic API (dev dependency) |
| Supabase | Deno edge functions | Backend telemetry + community stats |
| Chrome Extension Manifest V3 | — | Browser integration side panel |
1.4 Module Reference
browse/src/ — Core Browser Engine
| Module | Responsibility |
|---|---|
server.ts |
Persistent HTTP server — routes, lifecycle, port discovery |
cli.ts |
Thin CLI client — argument parsing, server auto-start |
browser-manager.ts |
BrowserManager class — Chromium lifecycle, tab management, viewport, context |
tab-session.ts |
TabSession class — per-tab refs, frames, loaded HTML state |
read-commands.ts |
Read-only commands: js, eval, css, attrs, console, network, source, text, refs |
write-commands.ts |
Mutating commands: click, fill, select, goto, load-html, upload, scroll, hover, wait |
meta-commands.ts |
Utility commands: pdf, screenshot, tabs, state, frame, mhtml |
snapshot.ts |
Accessibility tree extraction + annotated screenshots |
security-classifier.ts |
ML ensemble for prompt injection detection |
content-security.ts |
Content filtering — marks output between BEGIN/END UNTRUSTED markers |
url-validation.ts |
URL allowlist/blocklist, cloud metadata blocking, file:// handling |
path-security.ts |
File path validation — enforces safe directory allowlist |
activity.ts |
SSE real-time activity feed for Chrome extension |
audit.ts |
Append-only JSONL audit trail at .gstack/browse-audit.jsonl |
network-capture.ts |
SizeCappedBuffer for network request/response logging |
buffers.ts |
CircularBuffer for console, network, and dialog capture |
token-registry.ts |
Multi-agent token management with read/write/admin/meta/control scopes |
tunnel-denial-log.ts |
Logs denied tunnel access attempts |
sse-session-cookie.ts |
Session tracking for SSE connections |
sidebar-agent.ts |
Claude agent powering the Chrome sidebar chat |
cookie-import-browser.ts |
Import cookies from installed browsers (Chrome, Firefox, etc.) |
cookie-picker-routes.ts / cookie-picker-ui.ts |
Cookie selection UI and HTTP routes |
config.ts |
resolveConfig() — state dir, project detection |
platform.ts |
OS detection, temp directories |
error-handling.ts |
safeUnlink, safeKill, isProcessAlive utilities |
find-browse.ts |
Binary discovery for external callers |
media-extract.ts |
Media URL extraction from pages |
welcome.html |
Default page served in new tabs |
design/src/ — AI Design Tool
| Module | Responsibility |
|---|---|
cli.ts |
Entry point, subcommand routing |
auth.ts |
OpenAI API key resolution (env, config file) |
generate.ts |
Image generation via OpenAI Responses API |
iterate.ts |
Iterative design refinement with feedback loop |
evolve.ts |
Design evolution using vision feedback |
check.ts |
Vision-based quality gate (GPT-4o evaluates output) |
compare.ts |
Side-by-side design comparison |
gallery.ts |
Design gallery management |
serve.ts |
Local gallery HTTP server |
brief.ts |
Structured design brief interface |
session.ts |
Design session persistence |
memory.ts |
Design memory/preferences |
variants.ts |
A/B variant generation |
diff.ts |
Visual diff between designs |
design-to-code.ts |
Convert mockups to implementation code |
make-pdf/src/ — PDF Rendering Pipeline
| Module | Responsibility |
|---|---|
cli.ts |
Entry point, argument parsing |
orchestrator.ts |
ProgressReporter class — multi-file PDF orchestration |
render.ts |
Markdown to HTML to PDF rendering chain |
browseClient.ts |
Typed shell-out wrapper for browse CLI |
pdftotext.ts |
PDF text extraction for verification |
smartypants.ts |
Typography improvement (curly quotes, dashes) |
print-css.ts |
Print-optimized CSS injection |
setup.ts |
One-time setup routine |
types.ts |
BrowseClientError and shared types |
lib/ — Shared Libraries
| Module | Responsibility |
|---|---|
worktree.ts |
WorktreeManager class — Git worktree management for isolated testing |
scripts/ — Build & Generation
| Module | Responsibility |
|---|---|
gen-skill-docs.ts |
Multi-host skill documentation generation |
resolvers/index.ts |
Template resolver registry |
resolvers/browse.ts |
Command reference, snapshot flags, setup instructions |
resolvers/composition.ts |
INVOKE_SKILL cross-skill composition |
resolvers/confidence.ts |
Finding confidence calibration |
resolvers/constants.ts |
AI slop blacklist, design constants |
resolvers/design.ts |
Design review integration |
resolvers/codex-helpers.ts |
Codex frontmatter transformation |
resolvers/model-overlay.ts |
Model-specific behavior overlays |
resolvers/review.ts |
Code review resolver |
resolvers/review-army.ts |
Multi-agent review orchestration |
resolvers/testing.ts |
Test strategy resolver |
resolvers/gbrain.ts |
GBrain integration resolver |
resolvers/learnings.ts |
Cross-session learning resolver |
resolvers/make-pdf.ts |
PDF generation resolver |
resolvers/preamble.ts |
Skill preamble generation |
resolvers/question-tuning.ts |
Question quality tuning |
resolvers/dx.ts |
Developer experience resolver |
resolvers/utility.ts |
Utility helpers |
host-adapters/openclaw-adapter.ts |
OpenClaw-specific adapter |
2. System Requirements
2.1 Runtime Requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| Bun | >=1.0.0 | Latest stable |
| OS | macOS, Linux, Windows (WSL or native) | macOS or Linux |
| RAM | 2 GB free | 4+ GB (Chromium is memory-hungry) |
| Disk | 500 MB (Chromium + dependencies) | 1+ GB with cache |
| Network | Required for design tool, telemetry, tunneling | Broadband for headed browser use |
2.2 External Service Dependencies
| Service | Required For | Authentication |
|---|---|---|
| OpenAI API | design subcommands |
OPENAI_API_KEY env var |
| Anthropic API | Sidebar agent (dev) | ANTHROPIC_API_KEY env var |
| ngrok | Remote tunnel access | NGROK_AUTHTOKEN env var |
| Supabase | Telemetry, community pulse, update checks | Public anon key (in supabase/config.sh) |
Note
The browse server itself requires no external API keys. It runs entirely locally with Playwright-managed Chromium. API keys are only needed for the design tool (OpenAI) and optional features like tunneling (ngrok) or sidebar chat (Anthropic).
2.3 Supported AI Coding Hosts
gstack generates host-specific skill files for these environments:
| Host | Config File | Notes |
|---|---|---|
| Claude Code | hosts/claude.ts |
Primary target |
| OpenAI Codex CLI | hosts/codex.ts |
Codex frontmatter transformation |
| Cursor | hosts/cursor.ts |
— |
| Factory Droid | hosts/factory.ts |
— |
| Kiro | hosts/kiro.ts |
— |
| OpenCode | hosts/opencode.ts |
— |
| Slate | hosts/slate.ts |
— |
| OpenClaw | hosts/openclaw.ts |
Custom adapter in scripts/host-adapters/ |
| Hermes | hosts/hermes.ts |
— |
| GBrain | hosts/gbrain.ts |
Dedicated resolver |
3. Configuration Reference
gstack is configured entirely through environment variables. There are no YAML or TOML configuration files.
3.1 Browse Server Configuration
| Variable | Default | Description |
|---|---|---|
BROWSE_PORT |
Auto-discovered | Fix the server to a specific port |
BROWSE_IDLE_TIMEOUT |
Server default | Idle timeout before auto-shutdown (seconds) |
BROWSE_HEADED |
0 |
Set to 1 for headed Chromium (visible browser window) |
BROWSE_HEADLESS_SKIP |
0 |
Skip headless browser launch entirely |
BROWSE_PARENT_PID |
Parent PID | PID to watch — server exits when this process dies. Set 0 to disable watchdog |
BROWSE_NO_AUTOSTART |
0 |
Prevent CLI from auto-starting the server |
BROWSE_TAB |
— | Pin CLI operations to a specific tab ID |
BROWSE_SERVER_PORT |
— | Server port for sidebar agent communication |
BROWSE_STATE_FILE |
.gstack/browse-state.json |
Override path for server state file |
BROWSE_EXTENSIONS_DIR |
— | Directory containing Chrome extensions to load |
GSTACK_CHROMIUM_PATH |
Playwright default | Path to a custom Chromium binary |
GSTACK_BROWSE_MAX_HTML_BYTES |
— | Maximum HTML payload size for load-html command |
GSTACK_SLUG |
Auto-detected | Project slug identifier |
GSTACK_SKILL_ROOT |
— | Custom root directory for skill discovery |
Tip
For CI/CD pipelines,
BROWSE_PARENT_PID=0prevents the watchdog from killing the server if the parent shell exits. Combine withBROWSE_PORTfor deterministic port assignment in parallel test environments.
3.2 Security Configuration
| Variable | Default | Description |
|---|---|---|
GSTACK_SECURITY_OFF |
0 |
Set to 1 to disable the ML security classifier entirely |
GSTACK_SECURITY_ENSEMBLE |
All layers | Configure which ML ensemble layers to activate |
GSTACK_HAIKU_TIMEOUT_MS |
— | Timeout in ms for the Haiku classifier layer |
BROWSE_CONTENT_FILTER |
— | Content security filter mode for untrusted web content |
Warning
Setting
GSTACK_SECURITY_OFF=1disables prompt injection detection entirely. This removes the ML ensemble that protects against malicious web content being injected into AI agent conversations. Only disable for debugging or in fully trusted environments.
3.3 Tunnel Configuration
| Variable | Default | Description |
|---|---|---|
BROWSE_TUNNEL |
0 |
Enable ngrok tunnel for remote access |
NGROK_AUTHTOKEN |
— | ngrok authentication token (required for tunneling) |
NGROK_DOMAIN |
— | Custom ngrok domain |
3.4 Design Tool Configuration
| Variable | Default | Description |
|---|---|---|
OPENAI_API_KEY |
— | OpenAI API key for GPT-4o image generation and vision |
HOME |
System default | Home directory (used for API key file resolution) |
3.5 PDF Tool Configuration
| Variable | Default | Description |
|---|---|---|
BROWSE_BIN |
Auto-discovered | Override path to the browse binary |
PDFTOTEXT_BIN |
Auto-discovered | Override path to the pdftotext binary |
3.6 Platform / Environment Detection
| Variable | Default | Description |
|---|---|---|
CI |
— | When set, adds --no-sandbox to Chromium launch args |
CONTAINER |
— | When set, adds --no-sandbox to Chromium launch args |
PROGRAMFILES |
System default | Windows Program Files path (cookie import) |
USERPROFILE |
System default | Windows user profile path |
3.7 Sidebar Agent Configuration
| Variable | Default | Description |
|---|---|---|
SIDEBAR_QUEUE_PATH |
— | File path for sidebar agent message queue |
SIDEBAR_AGENT_TIMEOUT |
— | Timeout for sidebar agent operations |
BROWSE_SERVER_PORT |
— | Port the sidebar agent uses to connect to the browse server |
3.8 Testing & Eval Configuration
| Variable | Default | Description |
|---|---|---|
EVALS |
— | Enable eval test mode |
EVALS_ALL |
— | Run all eval tiers |
EVALS_BASE |
— | Base branch for eval comparison |
EVALS_TIER |
— | Eval tier selection (gate or periodic) |
EVALS_MODEL |
— | Model override for eval runs |
ANTHROPIC_API_KEY |
— | Anthropic API key for eval judge |
GSTACK_SDK_MAX_CONCURRENCY |
— | Maximum concurrent SDK sessions in test harness |
Note
The eval system supports tiered execution:
gateevals run on every PR, whileperiodicevals run on a schedule. SetEVALS_ALL=1to force all tiers in a single run.
4. Deployment & Scaling
4.1 Installation
# Clone the repository
git clone https://github.com/garrytan/gstack.git
cd gstack
# Install dependencies (Bun handles everything)
bun install
# Build the browse server bundle (required for Windows)
bun run build
4.2 Browse Server Lifecycle
The browse server follows a specific lifecycle:
- Auto-start: When the CLI sends a command and no server is running, it auto-starts one (unless
BROWSE_NO_AUTOSTARTis set). - Port discovery: The server tries a range of ports, or uses
BROWSE_PORTif set. If the port is taken, it fails with a clear error. - State file: On startup, the server writes its PID, port, and auth token to the state file (default:
.gstack/browse-state.json). - Idle timeout: After
BROWSE_IDLE_TIMEOUTseconds of no requests, the server shuts down. - Watchdog: If
BROWSE_PARENT_PIDis set and that process dies, the server exits.
# Start server explicitly with headed Chromium
BROWSE_HEADED=1 BROWSE_PORT=9222 bun run browse/src/server.ts
# Start with tunneling enabled
BROWSE_TUNNEL=1 NGROK_AUTHTOKEN=your_token bun run browse/src/server.ts
4.3 CI/CD Integration
For CI environments:
# Recommended CI environment variables
export CI=1 # Adds --no-sandbox for containerized Chromium
export BROWSE_PARENT_PID=0 # Disable watchdog
export BROWSE_PORT=9222 # Fixed port for predictability
export GSTACK_SECURITY_OFF=1 # Optional: skip ML classifier in trusted CI
Important
Chromium requires
--no-sandboxinside Docker containers and most CI runners. gstack automatically adds this flag whenCI=1orCONTAINER=1is set. Without it, the browser will fail to launch with a sandbox-related error.
4.4 Container Deployment
The browse server can run in Docker. Key considerations:
- Mount a volume for
.gstack/to persist state across restarts - Expose the browse server port
- Set
CONTAINER=1for automatic--no-sandbox - Chromium needs shared memory — use
--shm-size=2gor mount/dev/shm
FROM oven/bun:latest
WORKDIR /app
COPY . .
RUN bun install
ENV CONTAINER=1
ENV BROWSE_PORT=9222
EXPOSE 9222
CMD ["bun", "run", "browse/src/server.ts"]
docker run -d \
--shm-size=2g \
-p 9222:9222 \
-e CONTAINER=1 \
-e BROWSE_PORT=9222 \
-v gstack-state:/app/.gstack \
gstack-browse
4.5 Multi-Agent Deployment
gstack supports multiple AI agents sharing a single browse server through the token registry:
- The primary agent starts the server and gets the admin token from the state file.
- Additional agents connect via the
/pairendpoint to get scoped tokens. - Token scopes (
read,write,admin,meta,control) restrict what each agent can do.
This enables architectures where a supervisor agent delegates browsing tasks to worker agents, each with least-privilege access.
4.6 Remote Access via ngrok
For accessing the browse server from remote machines:
export BROWSE_TUNNEL=1
export NGROK_AUTHTOKEN=your_ngrok_token
export NGROK_DOMAIN=custom.ngrok.io # Optional: custom domain
# Server will log the tunnel URL on startup
bun run browse/src/server.ts
Caution
The ngrok tunnel exposes your browse server to the internet. While token-based authentication protects the
/commandendpoint, ensure your ngrok account has appropriate access controls. Denied tunnel access attempts are logged bytunnel-denial-log.ts.
5. Operations
5.1 HTTP API Endpoints
The browse server exposes these HTTP endpoints:
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /command |
Bearer token | Execute a browse command |
| GET | /health |
None | Server health check |
| POST | /connect |
None | Initial connection handshake |
| POST | /pair |
Bearer token | Register a new agent with scoped token |
| GET | /refs |
Bearer token | Get current page element references |
| GET | /activity/stream |
Bearer token | SSE real-time activity feed |
| GET | /activity/history |
Bearer token | Activity history |
| GET | /sidebar-tabs |
Bearer token | Sidebar tab listing |
| POST | /tunnel/start |
Bearer token | Start ngrok tunnel |
| GET | /file |
Bearer token | Serve local files |
| POST | /batch |
Bearer token | Execute multiple commands in sequence |
5.2 Server State File
The browse server persists its state to .gstack/browse-state.json (or the path in BROWSE_STATE_FILE). This file contains:
- Server PID
- Server port
- Authentication token
- Server start timestamp
The CLI reads this file to discover and authenticate with a running server. If the state file is stale (PID no longer alive), the CLI starts a new server.
5.3 Audit Log
Every command execution is logged to .gstack/browse-audit.jsonl as append-only JSONL. Each entry includes:
- Timestamp
- Command name and arguments
- Agent token (anonymized)
- Result status
- Execution duration
This provides a forensic trail for debugging multi-agent interactions and security incidents.
5.4 Telemetry
gstack sends anonymous telemetry to Supabase edge functions:
- telemetry-ingest: Batch insertion of usage events (max 100 events, max 50KB per request)
- community-pulse: Aggregated community statistics (cached for 1 hour)
- update-check: Version ping that logs installation info
Supabase configuration (public, non-secret) lives in supabase/config.sh.
Note
Telemetry is anonymous. Session tracking is gated by telemetry configuration (see issue #1150 for the gating fix).
5.5 Upgrade System
gstack includes a versioned migration system in gstack-upgrade/migrations/:
| Version | Migration | What It Does |
|---|---|---|
| v0.15.2.0 | Fix skill directory structure | Runs gstack-relink for unprefixed skill discovery |
| v0.16.2.0 | Merge resource logs | Migrates per-project resources-shown.jsonl to unified builder-profile.jsonl |
| v1.0.0.0 | V1 writing style | Prompts for ELI10 vs terse writing style preference |
| v1.1.3.0 | Remove /checkpoint | Uninstalls stale /checkpoint skill, replaces with /context-save + /context-restore |
Migrations run automatically during gstack updates and are idempotent.
5.6 Monitoring
Health check:
curl http://localhost:9222/health
Process monitoring:
# Check if server is alive using state file
cat .gstack/browse-state.json | jq '.pid' | xargs kill -0
Audit log tail:
tail -f .gstack/browse-audit.jsonl | jq .
6. Performance
6.1 Browser Resource Management
The BrowserManager manages Chromium's resource consumption:
- Tab limit: Each tab is a
TabSessionwith its own page context. More tabs means more memory. - Circular buffers: Console and network captures use
CircularBuffer(fixed-size ring buffers) to prevent unbounded memory growth. - Size-capped network capture:
SizeCappedBufferinnetwork-capture.tscaps total captured network data. - Idle timeout: The server auto-shuts down after inactivity, freeing all Chromium resources.
6.2 Chromium Launch Optimization
The server applies platform-specific Chromium flags:
--no-sandboxin CI/container environments- Anti-detection patches in headed mode (navigator.webdriver, plugins, languages, CDP artifacts, Permissions API)
- Custom Chromium binary via
GSTACK_CHROMIUM_PATHfor pre-warmed or optimized builds
6.3 PDF Rendering Performance
The make-pdf pipeline chains Markdown parsing (marked) through HTML rendering to Chromium's PDF output. For large documents:
- The
ProgressReporterclass inorchestrator.tsprovides real-time feedback during multi-file orchestration - Print-optimized CSS (
print-css.ts) ensures clean page breaks and typography smartypants.tsapplies typography improvements (curly quotes, em-dashes) in a single pass
6.4 Security Classifier Overhead
The ML security ensemble adds latency to command processing. If this is a bottleneck in trusted environments:
- Set
GSTACK_SECURITY_OFF=1to disable entirely - Configure
GSTACK_SECURITY_ENSEMBLEto activate only specific layers - Adjust
GSTACK_HAIKU_TIMEOUT_MSto cap the Haiku classifier timeout
Tip
In development, the security classifier can add noticeable latency to the first request while ML models load. Subsequent requests use cached models. Pre-warm the server by sending a no-op command after startup.
7. Security
7.1 Authentication Model
The browse server uses a token-based authentication system:
- Server token: Generated randomly on each server startup and written to the state file. Required as
Bearer <token>header on all/commandrequests. - Multi-agent tokens: The
TokenRegistry(browse/src/token-registry.ts) manages scoped tokens for paired agents.
Token scopes:
| Scope | Permissions |
|---|---|
read |
Read-only commands (js, eval, css, attrs, text, refs, source, console, network) |
write |
Mutating commands (click, fill, select, goto, load-html, upload, scroll, hover, wait) |
admin |
Server management commands |
meta |
Utility commands (pdf, screenshot, tabs, state, frame) |
control |
Tunnel and extension management |
7.2 ML Security Classifier
The security classifier (browse/src/security-classifier.ts) uses an ensemble of five detection layers to identify prompt injection attacks in web content:
| Layer | Method |
|---|---|
testsavant_content |
ML content classification |
transcript_classifier |
Transcript-based classification |
aria_regex |
Regular expression matching on ARIA/accessibility text |
canary |
Canary token detection |
deberta_content |
DeBERTa-based content classification |
The classifier produces verdicts: SAFE, WARN, or BLOCK. Attack telemetry (domain, payload hash, confidence, layer, verdict) is stored in Supabase via migration 004.
Important
Issue #1156 notes that WARN verdicts currently take no defensive action beyond logging. Until this is addressed, WARN-level detections are informational only. Monitor audit logs for WARN verdicts in production deployments.
7.3 Content Security
The content security module (browse/src/content-security.ts) filters untrusted web content before it reaches AI agents:
- All web-sourced content is wrapped between
BEGIN UNTRUSTED CONTENTandEND UNTRUSTED CONTENTmarkers - The
BROWSE_CONTENT_FILTERenvironment variable controls filtering aggressiveness - This prevents web content from being interpreted as instructions by downstream AI agents
7.4 URL Validation
browse/src/url-validation.ts implements:
- Cloud metadata blocking: Blocks access to
169.254.169.254and similar cloud metadata endpoints (SSRF prevention) file://validation: Restricts file URL access to safe directories- Allowlist/blocklist: Configurable URL filtering
7.5 Path Security
browse/src/path-security.ts enforces a safe directory allowlist for all file operations. Any path outside the allowed directories triggers the error:
Path must be within: <SAFE_DIRECTORIES>
7.6 Privacy Filtering
The activity stream (browse/src/activity.ts) automatically redacts:
- Passwords
- Authentication tokens
- Sensitive query parameters
This ensures the Chrome extension's side panel and activity stream do not leak credentials.
7.7 Cookie Security
BrowserManager tracks imported cookie domains to maintain an elevated security context for those origins. Cookie import (browse/src/cookie-import-browser.ts) supports extracting cookies from installed browsers.
7.8 Anti-Bot Stealth
In headed mode (BROWSE_HEADED=1), gstack patches Chromium to evade bot detection:
navigator.webdriverreturnsfalse- Plugin and language arrays are populated with realistic values
- CDP (Chrome DevTools Protocol) artifacts are hidden
- Permissions API behavior is normalized
7.9 Supabase Row-Level Security
The database migrations progressively tighten RLS policies:
- Reads blocked on all tables (telemetry data is write-only from clients)
- Updates restricted to tracking columns only (cannot modify event payload)
- Inserts kept for backward compatibility with older client versions
Verify RLS policies are correctly applied:
./supabase/verify-rls.sh
7.10 Audit Trail
The audit log (browse/src/audit.ts) writes append-only JSONL to .gstack/browse-audit.jsonl. This log is the forensic record for:
- All command executions
- Authentication events
- Security classifier verdicts
- Tunnel access attempts (denied attempts logged separately by
tunnel-denial-log.ts)
Warning
The audit log grows without bound. Implement log rotation for long-running deployments. The log file path is determined by the state directory (
.gstack/by default).
8. Developer Internals
8.1 Repository Structure
gstack/
├── browse/
│ ├── src/ # Browser automation engine
│ │ ├── server.ts # HTTP server entry point
│ │ ├── cli.ts # CLI client entry point
│ │ └── ... # 25+ modules (see Module Reference)
│ └── test/ # Browse tests
├── design/
│ └── src/ # AI design tool (15 modules)
├── make-pdf/
│ └── src/ # PDF pipeline (8 modules)
├── skills/ # SKILL.md.tmpl templates
├── scripts/
│ ├── resolvers/ # Template resolvers (18 resolvers)
│ ├── host-adapters/ # Host-specific adapters
│ └── gen-skill-docs.ts # Multi-host doc generator
├── hosts/ # Per-host config (10 hosts)
├── lib/ # Shared libraries (worktree.ts)
├── model-overlays/ # Model-specific behavior files
│ ├── claude.md
│ ├── gemini.md
│ ├── gpt.md
│ ├── gpt-5.4.md
│ ├── o-series.md
│ └── opus-4-7.md
├── extension/ # Chrome extension (Manifest V3)
├── supabase/
│ ├── functions/ # Edge functions (3)
│ ├── migrations/ # Schema migrations (001-004)
│ ├── config.sh # Public Supabase config
│ └── verify-rls.sh # RLS smoke test
├── gstack-upgrade/
│ └── migrations/ # Version migrations (4)
└── test/
└── helpers/ # Test utilities
8.2 Key Classes
| Class | Location | Purpose |
|---|---|---|
BrowserManager |
browse/src/browser-manager.ts |
Singleton managing Chromium lifecycle, tab creation/destruction, viewport, browser context |
TabSession |
browse/src/tab-session.ts |
Per-tab state container — element refs, frame navigation, loaded HTML tracking |
CircularBuffer |
browse/src/buffers.ts |
Fixed-size ring buffer for console and dialog capture |
SizeCappedBuffer |
browse/src/network-capture.ts |
Size-limited buffer for network request/response logging |
WorktreeManager |
lib/worktree.ts |
Git worktree creation and cleanup for isolated test environments |
ProgressReporter |
make-pdf/src/orchestrator.ts |
Progress tracking for multi-file PDF orchestration |
CookieImportError |
browse/src/cookie-import-browser.ts |
Error type for cookie import failures |
BrowseClientError |
make-pdf/src/types.ts |
Error type for browse CLI communication failures |
PdftotextUnavailableError |
make-pdf/src/pdftotext.ts |
Error type when pdftotext binary is not found |
RateLimitExhaustedError |
test/helpers/agent-sdk-runner.ts |
Error type for exhausted API rate limits in tests |
Semaphore |
test/helpers/agent-sdk-runner.ts |
Concurrency limiter for parallel SDK test sessions |
EvalCollector |
test/helpers/eval-store.ts |
Test evaluation result collector |
8.3 Skills System Architecture
Skills are the primary interface between gstack and AI coding hosts. The generation pipeline works as follows:
-
Templates: Each skill has a
SKILL.md.tmplfile with resolver placeholders like{{BROWSE_COMMAND_REFERENCE}},{{INVOKE_SKILL}},{{CONFIDENCE_CALIBRATION}}. -
Resolvers: TypeScript functions in
scripts/resolvers/that generate dynamic content. The resolver registry (scripts/resolvers/index.ts) maps placeholder names to resolver functions. -
Host configs: Each host in
hosts/defines path rewrites and frontmatter requirements. For example, Codex requires specific frontmatter transformation (scripts/resolvers/codex-helpers.ts). -
Generation:
scripts/gen-skill-docs.tsiterates over all templates, runs resolvers, applies host-specific transformations, and writes final.mdfiles. -
Model overlays: Files in
model-overlays/provide model-specific behavior adjustments (Claude, Gemini, GPT, GPT-5.4, O-series, Opus 4-7).
8.4 Command System
Browse commands are split into three categories by side-effect profile:
Read commands (browse/src/read-commands.ts) — no page mutation:
| Command | Description |
|---|---|
js / eval |
Execute JavaScript on the page |
css |
Query computed CSS properties |
attrs |
Get element attributes |
console |
Read console log buffer |
network |
Read network capture buffer |
source |
Get page source HTML |
text |
Extract text content |
refs |
Get element reference map |
Write commands (browse/src/write-commands.ts) — mutate page state:
| Command | Description |
|---|---|
goto |
Navigate to a URL |
load-html |
Load raw HTML into the page |
click |
Click an element |
fill |
Fill a form field |
select |
Select a dropdown option |
upload |
Upload a file |
scroll |
Scroll the page or element |
hover |
Hover over an element |
wait |
Wait for a selector or condition |
Meta commands (browse/src/meta-commands.ts) — server/session operations:
| Command | Description |
|---|---|
pdf |
Generate PDF of current page |
screenshot |
Capture screenshot |
tabs |
List or switch tabs |
state |
Save or load browser state |
frame |
Switch between frames |
mhtml |
Save page as MHTML archive (requires Chromium CDP) |
8.5 Snapshot System
The snapshot module (browse/src/snapshot.ts) produces accessibility tree representations and annotated screenshots. These are the primary mechanism by which AI agents "see" web pages:
- Accessibility tree: Extracts the page's a11y tree into a structured text format that AI agents can reason about
- Annotated screenshots: Captures the page with element reference overlays for visual grounding
8.6 Testing Strategy
gstack uses Bun's built-in test runner with a tiered evaluation system:
| Tier | Scope | When |
|---|---|---|
| Unit tests | Individual modules | Every change |
| Gate evals | Core functionality (EVALS_TIER=gate) |
Every PR |
| Periodic evals | Extended coverage (EVALS_TIER=periodic) |
Scheduled |
| Full evals | Everything (EVALS_ALL=1) |
Manual |
Test infrastructure:
test/helpers/agent-sdk-runner.ts: Parallel SDK session runner withSemaphorefor concurrency control andRateLimitExhaustedErrorhandlingtest/helpers/benchmark-judge.ts: LLM-based evaluation judge (uses Anthropic API)test/helpers/e2e-helpers.ts: End-to-end test helpers with eval mode gatingtest/helpers/eval-store.ts:EvalCollectorfor aggregating evaluation resultstest/helpers/session-runner.ts: Session management for eval runs (supportsEVALS_MODELoverride)
# Run unit tests
bun test
# Run gate evals
EVALS=1 EVALS_TIER=gate bun test
# Run all evals with a specific model
EVALS=1 EVALS_ALL=1 EVALS_MODEL=claude-3-5-sonnet bun test
8.7 Git Worktree Manager
lib/worktree.ts provides the WorktreeManager class for creating isolated Git worktrees. This is used by skills that need to run tests or builds in isolation without affecting the main working directory.
8.8 Code Patterns
Error handling: gstack uses safeUnlink, safeKill, and isProcessAlive utilities (browse/src/error-handling.ts) for defensive cleanup. Custom error classes (CookieImportError, BrowseClientError, PdftotextUnavailableError) provide typed error handling.
State management: The browse server treats the filesystem as persistent state. The state file, audit log, and session cookies are all file-based. resolveConfig() in browse/src/config.ts determines the state directory based on project detection.
Buffer management: Two buffer implementations prevent unbounded memory growth:
CircularBuffer(fixed item count, ring buffer) for console/dialog logsSizeCappedBuffer(fixed byte size) for network captures
Platform abstraction: browse/src/platform.ts handles OS detection and temp directory resolution, enabling cross-platform behavior in cookie import, path validation, and process management.
8.9 Contributing
Adding a New Browse Command
- Determine the command category (read, write, or meta).
- Add the handler function to the appropriate module (
read-commands.ts,write-commands.ts, ormeta-commands.ts). - Register the command in
server.ts's command dispatch. - Update the browse resolver (
scripts/resolvers/browse.ts) to include it in generated documentation. - Add tests.
Adding a New Skill
- Create a
SKILL.md.tmplfile in theskills/directory. - Use resolver placeholders (
{{RESOLVER_NAME}}) for dynamic content. - If a new resolver is needed, add it to
scripts/resolvers/and register it inscripts/resolvers/index.ts. - Run
bun run scripts/gen-skill-docs.tsto generate host-specific versions. - Test with each target host.
Adding a New Host
- Create a host config file in
hosts/(e.g.,hosts/newhost.ts). - Define path rewrites and frontmatter requirements.
- Register the host in
hosts/index.ts. - If the host needs special handling, create an adapter in
scripts/host-adapters/. - Run skill generation and verify output.
Adding a New Resolver
- Create a resolver module in
scripts/resolvers/. - Export a function that returns the resolved content string.
- Register the resolver name in
scripts/resolvers/index.ts. - Use the placeholder in skill templates:
{{YOUR_RESOLVER_NAME}}.
Adding a Supabase Migration
- Create a new migration file in
supabase/migrations/following the naming pattern00N-description.sql. - Apply the migration to your Supabase project.
- Update
supabase/verify-rls.shif the migration adds new tables or policies. - Test with the telemetry-ingest and community-pulse edge functions.
9. Integration Points
9.1 Chrome Extension
The Chrome extension (extension/) connects to the browse server for real-time interaction:
| File | Role |
|---|---|
manifest.json |
Chrome Manifest V3 configuration |
background.js |
Service worker — health polling, command proxy, ref relay |
sidepanel.js / sidepanel.html / sidepanel.css |
Activity stream display, chat interface |
content.js / content.css |
Page-level indicator injection, inspector fallback |
inspector.js / inspector.css |
Full element inspection overlay |
popup.html / popup.js |
Toolbar popup interface |
The extension communicates with the browse server via:
/healthfor connection status polling/activity/stream(SSE) for real-time event streaming/commandfor executing browse commands from the side panel/refsfor element reference data
9.2 Supabase Backend
Tables:
| Table | Purpose |
|---|---|
telemetry_events |
Raw usage events (write-only from clients) |
installations |
Installation tracking |
update_checks |
Version check history |
community_pulse_cache |
Cached aggregation for community stats |
Views:
| View | Purpose |
|---|---|
crash_clusters |
Aggregated crash data for pattern analysis |
skill_sequences |
Skill usage sequences for workflow detection |
Edge functions:
| Function | Method | Limits |
|---|---|---|
telemetry-ingest |
POST | Max 100 events, max 50KB per request |
community-pulse |
GET | Cached for 1 hour |
update-check |
GET | Logs installation info |
Tip
The
community-pulseendpoint caches results for one hour. During development, you may need to wait for cache expiration or clear thecommunity_pulse_cachetable directly to see updated aggregations.
9.3 Model Overlays
Model overlay files in model-overlays/ provide behavioral adjustments for different LLMs:
| Overlay | Target Model |
|---|---|
claude.md |
Claude (default) |
gemini.md |
Google Gemini |
gpt.md |
GPT-4 / GPT-4o |
gpt-5.4.md |
GPT-5.4 |
o-series.md |
OpenAI O-series (reasoning models) |
opus-4-7.md |
Claude Opus 4-7 |
These overlays are injected into skill templates via the model-overlay resolver (scripts/resolvers/model-overlay.ts) to tune prompt behavior for each model's strengths and characteristics.
9.4 make-pdf Integration
The make-pdf tool depends on the browse server for rendering:
Markdown → marked parser → HTML + print-css → browse load-html → Chromium PDF
make-pdf/src/browseClient.ts provides a typed wrapper around shell-outs to the browse CLI. Set BROWSE_BIN to override binary discovery. The pipeline includes:
- Setup (
setup.ts): One-time dependency check - Rendering (
render.ts): Markdown to HTML conversion with typography improvements - Orchestration (
orchestrator.ts): Multi-file coordination with progress reporting - Verification (
pdftotext.ts): Optional text extraction to verify PDF content
10. Troubleshooting
Error: Server failed to start within Ns
Cause: The browse server did not become ready within the startup timeout, typically because Chromium failed to launch.
Solution:
- Check that Chromium is installed:
bunx playwright install chromium - In containers, ensure
CI=1orCONTAINER=1is set for--no-sandbox - Verify sufficient shared memory:
df -h /dev/shm(need 256MB+) - Check for port conflicts:
lsof -i :9222
Prevention: Set BROWSE_PORT to avoid port scanning delays. Use GSTACK_CHROMIUM_PATH for a known-good binary.
Error: Cannot find server.ts. Set BROWSE_SERVER_SCRIPT env or run from the browse source tree.
Cause: The CLI cannot locate the server entry point. This happens when running the browse CLI from outside the gstack directory.
Solution:
- Run from within the gstack repository
- Or set the
BROWSE_SERVER_SCRIPTenvironment variable to the absolute path ofbrowse/src/server.ts
Prevention: Install gstack globally or use a shell alias that includes the correct working directory.
Error: server-node.mjs not found. Run 'bun run build' to generate the Windows server bundle.
Cause: On Windows, the browse server requires a pre-built bundle that has not been generated.
Solution:
bun run build
Prevention: Always run bun run build after cloning or updating the repository on Windows.
Error: Authentication failed (HTTP 401)
Cause: The CLI is using a stale token that does not match the running server's token.
Solution:
- Check the state file:
cat .gstack/browse-state.json - Verify the server PID is still alive
- If the server was restarted, the CLI will auto-discover the new token on the next command
- Delete the state file and let the CLI start a fresh server
Prevention: Let the CLI manage server lifecycle automatically rather than starting the server manually.
Error: [browse] Server crashed twice in a row — aborting
Cause: The server process crashed on two consecutive startup attempts.
Solution:
- Check system logs for Chromium crash details
- Verify system resources (RAM, disk space)
- Try with
BROWSE_HEADED=1to see if the browser window shows an error - Check for conflicting Chromium processes:
ps aux | grep chromium
Prevention: Monitor system resources. In CI, allocate sufficient --shm-size for Docker.
Error: Browser not launched
Cause: A command was sent before the browser context was initialized.
Solution:
- Wait for the server to fully start (check
/healthendpoint) - If using
BROWSE_HEADLESS_SKIP=1, the browser will not launch — unset this variable
Prevention: Use the CLI's auto-start mechanism rather than manual server management.
Error: Tab N not found
Cause: The specified tab ID does not exist or has been closed.
Solution:
- List available tabs:
browse tabs - Use a valid tab ID or omit
BROWSE_TABto use the active tab
Prevention: Do not hardcode tab IDs across sessions. Query available tabs before switching.
Error: No active page. Use browse goto <url> first.
Cause: The current tab has no page loaded (e.g., fresh server with no navigation).
Solution:
browse goto https://example.com
Prevention: Always navigate to a URL before running page commands.
Error: [browse] Port N (from BROWSE_PORT env) is in use
Cause: The port specified in BROWSE_PORT is already occupied by another process.
Solution:
- Find what is using the port:
lsof -i :N(ornetstat -tlnp | grep N) - Kill the process or choose a different port
- Unset
BROWSE_PORTto use automatic port discovery
Prevention: Use port ranges rather than fixed ports in multi-instance deployments.
Error: [browse] No available port after N attempts in range MIN-MAX
Cause: All ports in the auto-discovery range are occupied.
Solution:
- Check for zombie browse server processes:
ps aux | grep server.ts - Clean up stale state files:
rm .gstack/browse-state.json - Kill orphaned Chromium processes
Prevention: Ensure the watchdog (BROWSE_PARENT_PID) is active so servers auto-exit with their parent.
Error: MHTML archive requires Chromium CDP
Cause: The mhtml command was invoked but CDP (Chrome DevTools Protocol) is not available.
Solution: MHTML archiving requires a Chromium-based browser. Ensure you are not using a non-Chromium browser override via GSTACK_CHROMIUM_PATH.
Prevention: Only use mhtml with the default Playwright Chromium or a compatible Chromium binary.
Error: Path must be within: <SAFE_DIRECTORIES>
Cause: A file operation (upload, file serving) attempted to access a path outside the allowed directories.
Solution:
- Move the file to a directory within the allowlist
- Check
browse/src/path-security.tsfor the current safe directory list
Prevention: Keep working files within the project directory or configured safe directories.
Error: URL validation failures (cloud metadata, blocked domains)
Cause: The URL validation module blocked a request to a restricted URL.
Solution:
- For cloud metadata blocking (
169.254.169.254): this is intentional SSRF prevention — do not bypass - For
file://URLs: ensure the file path is within safe directories - Check URL against the allowlist/blocklist in
browse/src/url-validation.ts
Prevention: Use goto with HTTPS URLs. For local files, use load-html with file content instead of file:// URLs.
Error: Exit code 128 in conductor template creation
Cause: Git operations failed due to unborn HEAD or missing refs (GitHub issues #1174, #1176).
Solution:
- Ensure the repository has at least one commit
- Check that the current branch has a valid HEAD reference
- If using worktrees, verify the main repository is in a clean state
Prevention: Initialize repositories with an initial commit before running conductor skills.
Error: DESIGN_NOT_AVAILABLE in Codex workflows
Cause: Design tool is not available in the Codex environment (GitHub issue #1159).
Solution: The design tool requires OpenAI API access. Verify OPENAI_API_KEY is set and accessible in the Codex environment.
Prevention: Check host compatibility before invoking design workflows. Not all hosts support all gstack features.
Error: Custom OPENAI_BASE_URL not respected
Cause: The design tool does not honor custom OpenAI base URL configuration (GitHub issue #957).
Solution: Currently a known limitation. Use the standard OpenAI API endpoint or monitor issue #957 for a fix.
Prevention: Do not rely on custom base URLs for gstack's design features.
Error: ./setup --help runs install instead of showing help
Cause: The --help flag is not properly intercepted before the install flow (GitHub issue #1133).
Solution: Run gstack --help for general help instead.
Prevention: Refer to this guide for setup options.
Error: ./setup --prefix bug
Cause: The --prefix option in setup has a known issue (GitHub issue #620).
Solution: Use the default installation path or manually configure after setup.
Prevention: Monitor issue #620 for a fix.
Error: Review skill not looking at correct diff
Cause: The review skill examines the wrong diff base (GitHub issue #1152).
Solution: Explicitly specify the base branch when invoking the review skill, or ensure your Git branch tracking is configured correctly.
Prevention: Verify git diff output matches expectations before running the review skill.
Error: Codex generated skills carried Claude defaults
Cause: Host-specific skill generation was not properly transforming Claude defaults to Codex equivalents (GitHub issue #1163).
Solution: Regenerate skills with bun run scripts/gen-skill-docs.ts after updating to a version with this fix.
Prevention: After updating gstack, always regenerate skills for all hosts.
Error: WARN verdict takes no defensive action
Cause: The security classifier's WARN verdict logs the detection but does not block the content (GitHub issue #1156).
Solution: Monitor audit logs for WARN verdicts manually. Consider treating high-confidence WARNs as BLOCKs in your own middleware.
Prevention: Follow issue #1156 for the planned enforcement of WARN verdicts.
Caution
When debugging browse server issues, check the audit log (
.gstack/browse-audit.jsonl) first. It contains timestamped records of every command execution, authentication event, and security verdict, making it the fastest path to root cause.
11. Supabase Schema Reference
11.1 Tables
telemetry_events: Stores raw telemetry events. Client-writable (INSERT only), reads blocked by RLS.
installations: Tracks unique gstack installations. Populated by the update-check edge function.
update_checks: Version check history — logs which versions are checking for updates.
community_pulse_cache: Cached aggregation table used by the community-pulse edge function (1-hour TTL).
11.2 Views
| View | Purpose |
|---|---|
crash_clusters |
Aggregates crash data from telemetry events for pattern analysis |
skill_sequences |
Extracts skill usage sequences from telemetry to identify common workflows |
11.3 Migration History
| Migration | Description |
|---|---|
| 001 | Initial schema: telemetry_events, installations |
| 002 | Row-level security: block reads, restrict updates, keep inserts |
| 003 | Upsert support for telemetry deduplication |
| 004 | Attack telemetry columns: domain, payload_hash, confidence, layer, verdict |
Note
RLS policies are strict: clients can INSERT telemetry but cannot READ any data back. Telemetry is effectively write-only from the client perspective. All aggregation happens server-side via edge functions.
12. Quick Reference
Environment Variable Cheat Sheet
# Essential browse server config
export BROWSE_PORT=9222
export BROWSE_HEADED=1
export BROWSE_IDLE_TIMEOUT=3600
export BROWSE_PARENT_PID=0
# Security (development only)
export GSTACK_SECURITY_OFF=1
# Tunneling
export BROWSE_TUNNEL=1
export NGROK_AUTHTOKEN=your_token
# Design tool
export OPENAI_API_KEY=sk-your-key
# CI/containers
export CI=1
export CONTAINER=1
# Custom Chromium
export GSTACK_CHROMIUM_PATH=/usr/bin/chromium-browser
Common Operations
# Start headed browse server
BROWSE_HEADED=1 bun run browse/src/server.ts
# Health check
curl http://localhost:9222/health
# Run all tests
bun test
# Run gate evals
EVALS=1 EVALS_TIER=gate bun test
# Generate skill docs for all hosts
bun run scripts/gen-skill-docs.ts
# Build Windows bundle
bun run build
# Verify Supabase RLS
./supabase/verify-rls.sh
# Tail audit log
tail -f .gstack/browse-audit.jsonl | jq .
HTTP API Quick Reference
# Execute a command
curl -X POST http://localhost:9222/command \
-H "Authorization: Bearer $(jq -r .token .gstack/browse-state.json)" \
-H "Content-Type: application/json" \
-d '{"command": "goto", "args": ["https://example.com"]}'
# Pair a new agent with read-only access
curl -X POST http://localhost:9222/pair \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scopes": ["read"]}'
# Get element references
curl http://localhost:9222/refs \
-H "Authorization: Bearer $TOKEN"
# Stream activity (SSE)
curl -N http://localhost:9222/activity/stream \
-H "Authorization: Bearer $TOKEN"
See the User Guide for hands-on usage tutorials, CLI command reference, and workflow examples.