setup-gbrain skill (gstack) (part 2)

From Public Agent Wiki

Part 2 of 2 of setup-gbrain skill (gstack) (setup-gbrain/SKILL.md in garrytan/gstack); the SKILL.md text continues verbatim from the previous part.

SKILL.md (verbatim, continued)

Engine .......... ERR PGLite corrupt — run \gbrain restore-from-sync` (V1.5)). For V1, restore-from-sync is a V1.5 P0 cross-repo TODO; until it ships, the user's brain remote (with brain-sync enabled) holds curated artifacts as markdown + git, recoverable manually via gbrain import` from a clone.


/setup-gbrain --cleanup-orphans (D20)

Re-collect a PAT (show the Path 2a PAT scope disclosure — it lives in the brain-init section; read that section if it isn't already loaded), then:

# List user's Supabase projects (user has to pipe this through their own
# shell to review; we don't rely on a stored PAT).
export SUPABASE_ACCESS_TOKEN="<collected from read_secret_to_env>"
projects=$(curl -s -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
  https://api.supabase.com/v1/projects)

Parse the response, identify any project named starting with gbrain whose ref doesn't match the user's active ~/.gbrain/config.json pooler URL. For each orphan, AskUserQuestion per project: "Delete orphan project <ref> (<name>, created <created_at>)?" — NEVER batch; per-project confirm is a one-way door.

On confirmed delete:

curl -s -X DELETE -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
  https://api.supabase.com/v1/projects/$REF

Never delete the active brain without a second explicit confirmation.

At end: unset SUPABASE_ACCESS_TOKEN. Revocation reminder.


Telemetry (D4)

The preamble's Telemetry block logs skill success/failure at exit. When emitting the event, add these enumerated categorical values to the telemetry payload (SAFE — no free-form secrets, never the URL or PAT):

  • scenario: supabase-existing | supabase-auto-provision | supabase-manual | pglite-local | switch-to-supabase | switch-to-pglite | repo-flip-only | cleanup-orphans | resume-provision
  • install_performed: yes | no (D5 reuse) | skipped (pre-existing)
  • mcp_registered: yes | no | claude-missing
  • trust_tier_set: read-write | read-only | deny | skip-for-now | n/a (outside git repo)

Never pass SUPABASE_ACCESS_TOKEN, DB_PASS, GBRAIN_POOLER_URL, GBRAIN_DATABASE_URL, or any postgresql:// substring to the telemetry invocation. The CI grep test in test/skill-validation.test.ts enforces this at build time.


Important Rules

  • One rule for every secret. PAT, DB_PASS, pooler URL: env-var only, never argv, never logged, never persisted to disk by us. The only file that holds the pooler URL long-term is ~/.gbrain/config.json, written by gbrain's own init at mode 0600 — that's gbrain's discipline, not ours.
  • STOP points are hard. Gbrain doctor not healthy, D19 PATH shadow, D9 migrate timeout, smoke test failure — each is a STOP. Do not paper over.
  • Concurrent-run lock. At skill start, mkdir ~/.gstack/.setup-gbrain.lock.d (atomic). If the mkdir fails, abort with: "Another /setup-gbrain instance is running. Wait for it, or rm -rf ~/.gstack/.setup-gbrain.lock.d if you're sure it's stale." Release on normal exit AND in the SIGINT trap.
  • CLAUDE.md is the audit trail. Always update it in Step 8 after a successful setup.

Other files in this skill

memory.md (verbatim)

gstack memory ingest — what it does, what stays local, what you can do with it

This is the user-facing reference for the V1 transcript + memory ingest feature in /setup-gbrain. If you ran /setup-gbrain and it asked "Ingest THIS repo's transcripts into gbrain?", this doc explains what happens after you say yes.

What gets ingested

Source Type Where Sensitivity
Claude Code session JSONL transcript ~/.claude/projects/*/ High — full conversations including tool I/O
Codex CLI session JSONL transcript ~/.codex/sessions/YYYY/MM/DD/ High
Cursor session SQLite (V1.0.1) transcript ~/Library/Application Support/Cursor/ Same — deferred V1.0.1
Eureka log eureka ~/.gstack/analytics/eureka.jsonl Medium — your insights, often non-secret
Project learnings learning ~/.gstack/projects/<slug>/learnings.jsonl Medium
Project timeline timeline ~/.gstack/projects/<slug>/timeline.jsonl Low
CEO plans ceo-plan ~/.gstack/projects/<slug>/ceo-plans/*.md Medium
Design docs design-doc ~/.gstack/projects/<slug>/*-design-*.md Medium
Retros retro ~/.gstack/projects/<slug>/retros/*.md Medium
Builder profile builder-profile-entry ~/.gstack/builder-profile.jsonl Low

What stays local

  • State files (~/.gstack/.gbrain-sync-state.json, ~/.gstack/.transcript-ingest-state.json, ~/.gstack/.gbrain-engine-cache.json, ~/.gstack/.gbrain-errors.jsonl) are local-only per ED1 (state file sync semantics decision). They are not synced via the brain remote.

  • Sessions with no resolvable git remote (running in /tmp/, scratch dirs, etc.) are skipped by default. Pass --include-unattributed to the ingest helper to opt them in.

  • Repos under a deny trust policy (set in /setup-gbrain Step 6) are skipped — neither code nor transcripts from those repos ingest.

Per-remote trust policy (deny / read-only)

Transcript ingest respects the same per-remote trust store as code import (~/.gstack/gbrain-repo-policy.json, managed by gstack-gbrain-repo-policy). Each transcript's git remote is checked against the store before anything is written:

  • deny — the transcript is skipped (reported as skipped (policy deny)).
  • read-only — skipped too: read-only means "search allowed, page writes never", and transcript ingest writes pages (reported as skipped (policy read-only)).
  • read-write, or no entry — ingests normally.
  • Corrupted or unreadable store — ingestion aborts before any writes rather than bypassing a set policy. Inspect the store with gstack-gbrain-repo-policy list; re-run /setup-gbrain if it's corrupt.

Artifacts (learnings, plans, retros, etc.) are never policy-filtered — the policy is keyed by git remote, which artifacts don't have.

What gets scanned for secrets

The cross-machine secret boundary is gstack-brain-sync (the git push to your private artifacts repo), which runs its own scanner before any content leaves this Mac. Local PGLite ingest doesn't change the exposure surface for content that already lives on disk in plaintext.

Per-file gitleaks scanning during memory ingest is opt-in as of v1.33.0.0 — off by default. To re-enable it (adds ~4-8 min to cold runs on a large transcript corpus), use either:

bun run bin/gstack-memory-ingest.ts --bulk --scan-secrets
# or
GSTACK_MEMORY_INGEST_SCAN_SECRETS=1 bun run bin/gstack-memory-ingest.ts --bulk

When enabled, gitleaks covers:

  • AWS / GCP / Azure access keys
  • ANTHROPIC_API_KEY, OPENAI_API_KEY, GitHub tokens
  • Stripe keys, Slack tokens, JWT secrets
  • Generic high-entropy strings (configurable threshold)

A session with a positive finding is skipped entirely — not partially redacted. The match line + rule ID are logged to stderr; you can see what was skipped via bun run bin/gstack-memory-ingest.ts --probe (which shows new vs. updated counts) or by reviewing the helper's output during /sync-gbrain --full.

If gitleaks is not installed (run brew install gitleaks on macOS, or apt install gitleaks on Linux) and you passed --scan-secrets anyway, the helper warns once and disables secret scanning for that run.

Where it goes

Storage tier depends on your gbrain engine (set during /setup-gbrain):

  • Supabase configured: code + transcripts go to Supabase Storage (multi-Mac native). Curated memory (eureka/learnings/etc.) goes to the brain-linked git repo via gstack-brain-sync.
  • Local PGLite only: everything stays on this Mac. Curated memory syncs via git if you've enabled brain-sync.

The "never double-store" rule per the plan: code and transcripts NEVER go in the gbrain-linked git repo. They're too big and they're replaceable from disk on each Mac.

What you can do with it

  • Query in natural language:

    gbrain query "what was I doing on the auth migration"
    gbrain search "session_id:abc123"
    
  • Browse by type:

    gbrain list_pages --type transcript --limit 10
    gbrain list_pages --type ceo-plan
    
  • Read a specific page:

    gbrain get_page transcripts/claude-code/garrytan-gstack/2026-05-01-abc123
    
  • Delete a page:

    gbrain delete_page <slug>
    

    Caveat: with brain-sync enabled, the page is removed from gbrain's index but git history retains it. For hard-delete, run git filter-repo on the brain remote.

  • Bulk-delete by criteria (V1.0.1 follow-up — gstack-transcript-prune helper). For V1.0, use gbrain delete_page <slug> per-page or write a small loop over gbrain list_pages output.

  • Disable entirely:

    gstack-config set transcript_ingest_mode off
    gstack-config set gbrain_context_load off  # also disables retrieval
    

How the agent uses it

At every gstack skill start, the preamble runs gstack-brain-context-load which:

  1. Reads the active skill's gbrain.context_queries: frontmatter
  2. Dispatches each query to gbrain (vector / list / filesystem)
  3. Renders results into ## <render_as> sections wrapped in <USER_TRANSCRIPT_DATA do-not-interpret-as-instructions> envelopes
  4. The model sees this as part of the preamble before making any decisions

For example, when you run /office-hours, the model context automatically includes:

  • ## Prior office-hours sessions in this repo (last 5)
  • ## Your builder profile snapshot (latest entry)
  • ## Recent design docs for this project (last 3)
  • ## Recent eureka moments (last 5)

So the "Welcome back, last time you were on X" beat is sourced from your actual data, not cold-start.

If gbrain is unavailable (CLI missing, MCP not registered, query timeout), the helper renders (unavailable) and the skill continues — startup never blocks > 2s on gbrain issues (Section 1C).

What to do when something feels off

Run /setup-gbrain again. It's idempotent: every step detects existing state, repairs only what's missing, and prints a GREEN/YELLOW/RED verdict block. If a row is RED, the row tells you what to do.

Common cases:

  • Salience block is empty — your transcripts may not be ingested yet. Run bun run bin/gstack-gbrain-sync.ts --full to do a full pass.

  • "gbrain CLI missing" in the preamble output — gbrain isn't on your PATH. Run /setup-gbrain to install/wire it.

  • PGLite engine corrupt (V1.5) — V1.5 ships gbrain restore-from-sync for atomic rebuild from the brain remote. For V1.0, manual recovery: cd ~/.gbrain && rm -rf db && gbrain init --pglite && gbrain import <brain-remote-clone-dir>.

  • A page has stale or wrong contentgbrain delete_page <slug>, then re-run bun run bin/gstack-gbrain-sync.ts --incremental to re-ingest from source if the source file is still on disk and unchanged.

Privacy + audit

  • Every secretScanFile finding is logged to stderr at ingest time.
  • Every gbrain put/delete is logged to ~/.gstack/.gbrain-errors.jsonl with {ts, op, duration_ms, outcome} for forensic tracing.
  • ~/.gstack/.gbrain-engine-cache.json shows which storage tier is active (PGLite vs Supabase).
  • Brain-sync git history shows every curated artifact push with the user's git identity.

If you find a transcript page that contains a secret (either because per-file scanning was off, or gitleaks missed it), the recovery path is:

  1. gbrain delete_page <slug> — removes from index immediately
  2. Rotate the secret (rotate it anyway as a defensive measure)
  3. If brain-sync is on: git filter-repo --invert-paths --path <relative-path> on the brain remote for hard-delete from history
  4. If the miss looks like a gitleaks rule gap, file a gitleaks issue with the pattern (or extend the gitleaks config at ~/.gitleaks.toml).

Path 4: Remote MCP setup (v1.27.0.0+)

If you don't run gbrain locally — you have a teammate or another machine running gbrain serve over HTTP, accessible via Tailscale, ngrok, or internal LAN — /setup-gbrain Path 4 is the one-paste flow.

You provide:

  • The MCP URL (e.g., https://wintermute.tail554574.ts.net:3131/mcp)
  • A bearer token (issued by the brain admin via gbrain access-token issue)

What /setup-gbrain does:

  1. Verifies the URL + token via gstack-gbrain-mcp-verify. Three failure modes get classified with one-line remediation hints: NETWORK ("check Tailscale/DNS"), AUTH ("rotate token"), MALFORMED ("Accept-header gotcha — pass both application/json AND text/event-stream").
  2. Registers the MCP at user scope:
    claude mcp add --scope user --transport http gbrain "$URL" \
      --header "Authorization: Bearer $TOKEN"
    
  3. Skips local install, local doctor, transcript ingest, and federated source registration. All four require a local gbrain CLI that Path 4 doesn't install.
  4. Optionally provisions a gstack-artifacts-$USER private repo on GitHub or GitLab and prints the one-line gbrain sources add command for your brain admin to run on the brain host.

Token storage trade-off

The bearer token lives in ~/.claude.json (mode 0600), where Claude Code stores every MCP server's credentials. During claude mcp add --header "Authorization: Bearer $TOKEN", the token is briefly visible in process argv (~10ms) — visible to ps running concurrently. The window is small but it's not zero.

Mitigations we've considered:

  • Stdin or env-var input form for headers — would close the argv window. As of Claude Code v1.0.x, the CLI doesn't expose either. When it does, /setup-gbrain Path 4 will switch automatically.
  • Keychain storage — explicitly out of scope (the token's resting state in ~/.claude.json is the existing trust surface for every MCP credential; expanding to Keychain would touch every MCP server, not just gbrain).

Why Path 4 is "always print" for the brain-admin hookup

gstack-artifacts-init always prints the gbrain sources add command labeled "Send this to your brain admin" — even when the user IS the brain admin (consistent UX, no mode-detection fragility).

A previous design proposed probing whether the user's bearer has admin scope (via a benign MCP write call like add_tag) and auto-executing the source registration when scope was sufficient. The design review flagged that page-write doesn't actually prove source-management permission — those are different scopes in any sensible auth model. Until gbrain ships:

  • a mcp__gbrain__whoami capability tool that returns the bearer's scope set, AND
  • a mcp__gbrain__sources_add MCP tool with admin-scope gating

we always print the command rather than pretending we know who has permission to run it.

CLAUDE.md block in Path 4

Distinct from local-stdio mode. Token is never written to CLAUDE.md (many projects check CLAUDE.md into git). The block records the URL, the verified server version, the artifacts repo URL (if provisioned), and the per-repo trust policy.

## GBrain Configuration (configured by /setup-gbrain)
- Mode: remote-http
- MCP URL: https://wintermute.tail554574.ts.net:3131/mcp
- Server version: gbrain v0.27.1
- Setup date: 2026-05-06
- MCP registered: yes (user scope)
- Token: stored in ~/.claude.json (do not commit; never written to CLAUDE.md)
- Artifacts repo: github.com/garrytan/gstack-artifacts-garrytan (private)
- Artifacts sync: artifacts-only
- Current repo policy: read-write

Token rotation

Server-side. When verify hits AUTH (e.g., the brain admin rotated the token), the helper says: "rotate token on the brain host, re-run /setup-gbrain." On wintermute or wherever your gbrain server lives:

gbrain access-token rotate    # invalidates old, issues new

(See gstack/setup-gbrain/SKILL.md.tmpl for the full Path 4 flow plus the gbrain enhancement requests around scoped tokens that would let gstack auto-rotate in V2.)

sections/brain-init.md (verbatim)

<!-- AUTO-GENERATED from brain-init.md.tmpl — do not edit directly --> <!-- Regenerate: bun run gen:skill-docs -->

Path-specific. Run ONLY the sub-section below for the path picked in Step 2 (or the Switch flow when Step 2 chose engine migration).

Path 1 (Supabase, existing URL)

Source the secret-read helper, collect URL with read -s + redacted preview:

. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
read_secret_to_env GBRAIN_POOLER_URL "Paste Session Pooler URL: " \
  --echo-redacted 's#://[^@]*@#://***@#'

Then validate structurally:

printf '%s' "$GBRAIN_POOLER_URL" | ~/.claude/skills/gstack/bin/gstack-gbrain-supabase-verify -

If the verify exit code is 3 (direct-connection URL), the verifier's own message explains the fix; surface it and re-prompt for a Session Pooler URL.

On success, hand off to gbrain via env var (D10, never argv):

GBRAIN_DATABASE_URL="$GBRAIN_POOLER_URL" gbrain init --non-interactive --json

Then unset GBRAIN_POOLER_URL GBRAIN_DATABASE_URL immediately. The URL is now persisted in ~/.gbrain/config.json at mode 0600 by gbrain itself.

Path 2a (Supabase, auto-provision — D7)

Show the D11 PAT scope disclosure verbatim BEFORE collecting the token:

This Supabase Personal Access Token grants full read/write/delete access to every project in your Supabase account, not just the gbrain one we're about to create. Supabase doesn't currently support scoped tokens. We use this PAT only to: create one project, poll it until healthy, read the Session Pooler URL — then discard it from process memory. The token remains valid on Supabase's side until you manually revoke it at https://supabase.com/dashboard/account/tokens — we recommend revoking immediately after setup completes.

Then:

. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
read_secret_to_env SUPABASE_ACCESS_TOKEN "Paste PAT: "

Ask the D17 tier prompt via AskUserQuestion: "Which Supabase tier?" Present Free (2-project limit, pauses after 7d inactivity) vs Pro ($25/mo, no pauses, recommended for real use). Explain that tier is org-level (per the Management API contract) — user picks their org based on its current tier. Pro may require them to upgrade the org first at supabase.com.

List orgs, pick one (AskUserQuestion if multiple):

orgs=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision list-orgs --json)

If the .orgs array is empty, surface: "Your Supabase account has no organizations. Create one at https://supabase.com/dashboard, then re-run /setup-gbrain." STOP.

Ask the user for a region (default us-east-1; valid values are the 18 enum values in the Supabase Management API — list a few common ones, let them pick "Other" for a full list).

Generate the DB password (never shown to the user):

export DB_PASS=$(openssl rand -base64 24)

Set up a SIGINT trap (D12 basic recovery):

trap 'echo ""; echo "gstack-gbrain: interrupted. In-flight ref: $INFLIGHT_REF"; \
      echo "Resume: /setup-gbrain --resume-provision $INFLIGHT_REF"; \
      echo "Delete: https://supabase.com/dashboard/project/$INFLIGHT_REF"; \
      unset SUPABASE_ACCESS_TOKEN DB_PASS; exit 130' INT TERM

Create + wait + fetch:

result=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
  create gbrain "$REGION" "$ORG_SLUG" --json)
INFLIGHT_REF=$(echo "$result" | jq -r .ref)
~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision wait "$INFLIGHT_REF" --json
pooler=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
  pooler-url "$INFLIGHT_REF" --json)
GBRAIN_DATABASE_URL=$(echo "$pooler" | jq -r .pooler_url)
export GBRAIN_DATABASE_URL
gbrain init --non-interactive --json
unset SUPABASE_ACCESS_TOKEN DB_PASS GBRAIN_DATABASE_URL INFLIGHT_REF
trap - INT TERM

After success, emit the PAT revocation reminder:

"Setup complete. Revoke the PAT you pasted at https://supabase.com/dashboard/account/tokens — we've already discarded it from memory and don't need it again. The gbrain project will continue working because it uses its own embedded database password."

Path 2b (Supabase, manual)

Walk the user through the supabase.com steps:

  1. Login at https://supabase.com/dashboard
  2. Click "New Project," name it gbrain, pick a region, copy the generated database password (you'll need it for paste-back? no — it's embedded in the pooler URL we collect next)
  3. Wait ~2 min for the project to initialize
  4. Settings → Database → Connection Pooler → Session → copy the URL (port

Then follow the same secret-read + verify + init flow as Path 1.

Path 3 (PGLite local)

# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — code
# retrieval beats general-purpose embeddings on real code queries (validated
# A/B). Without the key, gbrain auto-selects (OpenAI 1536d when available).
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
set --  # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
if [ -n "${VOYAGE_API_KEY:-}" ]; then
  set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
fi
gbrain init --pglite --json "$@"

Done. No network, no secrets (beyond Voyage embedding API calls during sync, if VOYAGE_API_KEY is set — ~$0.18 per 1M tokens, pennies per repo).

Path 4 (Remote gbrain MCP — HTTP transport with bearer token)

For users whose brain runs on another machine (Tailscale, ngrok, internal LAN, or a teammate's server). No local gbrain CLI install, no local DB. This skill registers the remote MCP and stops; ingestion + indexing happens on the brain host.

4a. Collect MCP URL. Prompt the user:

Paste your gbrain MCP URL (e.g. https://wintermute.tail554574.ts.net:3131/mcp):

Read with plain read -r (no secret hygiene needed — the URL alone isn't a credential). Validate it starts with https:// (require TLS for any non-loopback host); refuse http:// for non-localhost.

4b. Collect bearer token via the secret-read helper (D10, never argv).

. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
read_secret_to_env GBRAIN_MCP_TOKEN "Paste bearer token: " \
  --echo-redacted 's/.\{6\}$/***REDACTED***/'

4c. Verify via gstack-gbrain-mcp-verify. Run the helper; capture the classified JSON output:

verify_json=$(GBRAIN_MCP_TOKEN="$GBRAIN_MCP_TOKEN" \
  ~/.claude/skills/gstack/bin/gstack-gbrain-mcp-verify "$MCP_URL")
status=$(echo "$verify_json" | jq -r .status)

If status != "success", the helper has already classified the failure into NETWORK / AUTH / MALFORMED and emitted a one-line remediation hint. Surface the hint above the raw error from error_text and STOP with a clear "fix and re-run /setup-gbrain" message. Do NOT continue to Step 5a on a failed verify — partial registration would leave the user with a half-broken state.

Capture two values from the verify output for downstream steps:

  • SERVER_VERSION (e.g., 0.27.1) — written to the CLAUDE.md block in Step 8.
  • URL_FORM_SUPPORTED (true|false) — passed to gstack-artifacts-init in Step 7 to control which form of the brain-admin hookup command is printed.

4d. (Path 4) Offer local PGLite for code search. Per plan D10/D11, ask:

D# — Want symbol-aware code search on this machine? Project/branch/task: <one-sentence grounding using detected slug + branch> ELI10: The remote brain at <MCP_URL> is great for cross-machine knowledge, but symbol queries like gbrain code-def / code-refs / code-callers need a local index of THIS machine's code. We can spin up a tiny isolated PGLite database (~30 seconds, no accounts, ~120 MB disk) just for code, separate from your remote brain. Transcripts and artifacts continue routing through the artifacts repo to the remote brain — local PGLite stays code-only. Stakes: without it, semantic code search in this repo's worktrees falls back to Grep. Recommendation: A — 30 seconds, no ongoing cost, unlocks the symbol tools. Completeness: A=10/10 (full split-engine), B=7/10 (remote-only). A) Yes, set up local PGLite for code (recommended) ✅ Unlocks gbrain code-def, code-refs, code-callers per worktree ✅ Independent engine — won't disturb remote brain or share transcripts B) No, remote MCP only ✅ Zero local state — only ~/.claude.json MCP registration ❌ Symbol code queries fall back to Grep in this repo's worktrees Net: A = full split-engine; B = remote-only.

If A (Yes): install + init local PGLite with rollback-safe semantics (D7):

~/.claude/skills/gstack/bin/gstack-gbrain-install || exit $?
# At this point the local gbrain CLI is on PATH. Init PGLite, but back up any
# existing ~/.gbrain/config.json first (rollback if init fails).
if [ -f "$HOME/.gbrain/config.json" ]; then
  BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
  mv "$HOME/.gbrain/config.json" "$BACKUP"
fi
# gstack default for local code-search PGLite: voyage-code-3 (1024d) when
# VOYAGE_API_KEY is set. It wins the A/B over voyage-4-large and OpenAI
# text-embedding-3-large on this codebase's symbol queries. Falls back to
# gbrain's auto-selected provider when the key isn't present.
set --  # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
if [ -n "${VOYAGE_API_KEY:-}" ]; then
  set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
fi
if ! gbrain init --pglite --json "$@"; then
  if [ -n "${BACKUP:-}" ] && [ -f "$BACKUP" ]; then mv "$BACKUP" "$HOME/.gbrain/config.json"; fi
  echo "gbrain init failed. Existing config (if any) was restored. PGLite at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` to reset." >&2
  echo "Continuing setup without local code search; you can re-run /setup-gbrain to retry." >&2
fi

Then continue to Step 5a. The remote-http MCP registration in 5a runs as today; the local PGLite is independent of MCP registration (Claude Code talks to the remote brain via MCP for queries; gbrain CLI talks to local PGLite for code-def/refs/callers).

If B (No): skip the install + init. The local engine stays absent. gbrain_local_status will be missing-config (or no-cli if gbrain isn't installed). /sync-gbrain will SKIP the code stage cleanly per plan D12.

4e. Skip Steps 3, 4 (other paths) and 5 (local doctor) when B was picked. When A was picked, Step 3 already ran (via gstack-gbrain-install) and Step 4 already ran (via gbrain init --pglite); jump straight to Step 5a. When B was picked, Steps 3/4/5 are no-ops; also skip Step 7.5 (transcript ingest) since memory-stage routes through the artifacts pipeline in remote-http mode per plan D11.

The bearer token (GBRAIN_MCP_TOKEN) stays in process env until Step 5a's claude mcp add --header consumes it; then unset GBRAIN_MCP_TOKEN immediately. Token security trade-off documented in setup-gbrain/memory.md: brief argv exposure during claude mcp add, resting state in ~/.claude.json mode 0600.

Switch (from detect's existing-engine state)

# Going PGLite → Supabase, collect URL first (Path 1 flow), then:
timeout 180s gbrain migrate --to supabase --url "$URL" --json
# Going Supabase → PGLite:
timeout 180s gbrain migrate --to pglite --json

If timeout returns 124 (exit code for timeout): surface D9 message ("Migration didn't complete in 3 minutes — another gstack session may be holding a lock on the source brain. Close other workspaces and re-run /setup-gbrain --switch. Your original brain is untouched."). STOP.

sections/claude-md-persist.md (verbatim)

<!-- AUTO-GENERATED from claude-md-persist.md.tmpl — do not edit directly --> <!-- Regenerate: bun run gen:skill-docs -->

Find-and-replace (or append) the section. Block format depends on mode:

Path 4 (Remote MCP)

## GBrain Configuration (configured by /setup-gbrain)
- Mode: remote-http
- MCP URL: {MCP_URL}
- Server version: gbrain v{SERVER_VERSION}  (from Step 4c verify)
- Setup date: {today}
- MCP registered: yes (user scope)
- Token: stored in ~/.claude.json (do not commit; never written to CLAUDE.md)
- Artifacts repo: {gstack_artifacts_remote URL or "none"}
- Artifacts sync: {off|artifacts-only|full}
- Current repo policy: {read-write|read-only|deny|unset}

The bearer token is never written to CLAUDE.md (CLAUDE.md is checked in to git in many projects). It lives only in ~/.claude.json where claude mcp add placed it.

Paths 1, 2a, 2b, 3 (Local stdio)

## GBrain Configuration (configured by /setup-gbrain)
- Mode: local-stdio
- Engine: {pglite|postgres}
- Config file: ~/.gbrain/config.json (mode 0600)
- Setup date: {today}
- MCP registered: {yes/no}
- Artifacts sync: {off|artifacts-only|full}
- Current repo policy: {read-write|read-only|deny|unset}

After Step 9 (smoke test) passes, also write the ## GBrain Search Guidance block so the coding agent learns when to prefer gbrain over Grep. This block is gated on the smoke test passing — write the Configuration block first (so the user knows what state they're in even if the smoke test fails), then return here after Step 9 and write the guidance block only if smoke test succeeded.

When Step 9 passes, find-and-replace (or append) this block. Use HTML-comment delimiters so removal regex is unambiguous and never eats user content. The block content is machine-AGNOSTIC — no engine type, no page counts, no last-sync time. Machine state stays in the Configuration block above.

## GBrain Search Guidance (configured by /sync-gbrain)
<!-- gstack-gbrain-search-guidance:start -->

GBrain is set up and synced on this machine. The agent should prefer gbrain
over Grep when the question is semantic or when you don't know the exact
identifier yet. Two indexed corpora available via the `gbrain` CLI:
- This repo's code (registered as `gstack-code-<repo>` source).
- `~/.gstack/` curated memory (registered as `gstack-brain-<user>` source via
  the existing federation pipeline).

Prefer gbrain when:
- "Where is X handled?" / semantic intent, no exact string yet:
    `gbrain search "<terms>"` or `gbrain query "<question>"`
- "Where is symbol Y defined?" / symbol-based code questions:
    `gbrain code-def <symbol>` or `gbrain code-refs <symbol>`
- "What calls Y?" / "What does Y depend on?":
    `gbrain code-callers <symbol>` / `gbrain code-callees <symbol>`
- "What did we decide last time?" / past plans, retros, learnings:
    `gbrain search "<terms>" --source gstack-brain-<user>`

Grep is still right for known exact strings, regex, multiline patterns, and
file globs. The brain auto-syncs incrementally on every gstack skill start.
Run `/sync-gbrain` to force-refresh, `/sync-gbrain --full` for full reindex.

<!-- gstack-gbrain-search-guidance:end -->

If Step 9 smoke test fails, skip the guidance block write entirely. The user's next /sync-gbrain run will re-evaluate capability and write the block when the round-trip works.

sections/engine-remediation.md (verbatim)

<!-- AUTO-GENERATED from engine-remediation.md.tmpl — do not edit directly --> <!-- Regenerate: bun run gen:skill-docs -->

Step 1.5 remediation: broken-engine AskUserQuestion + repair branches

The user has a non-working local engine (Garry's repro: ~/.gbrain/config.json points at a dead Postgres URL). Fire a targeted AskUserQuestion BEFORE Step 2:

D# — Your local gbrain engine isn't responding. How do you want to fix it? Project/branch/task: <one-sentence grounding using detected slug + branch> ELI10: gbrain has a config at ~/.gbrain/config.json but the engine it points at isn't reachable. That could be a transient outage (Postgres container stopped, Tailscale down) OR a stale config you want to abandon. Different remediation for each case. Stakes if we pick wrong: "Switch to PGLite" overwrites your existing config (one-way door if the user actually wanted the broken engine). "Retry" preserves existing state for transient cases. Recommendation: A (Retry) — always try the cheap option first; if engine is just temporarily down it'll come back without any destructive change. Note: options differ in kind, not coverage — no completeness score. A) Retry — re-probe the engine (recommended; ~80ms) ✅ Cheapest test: re-runs gbrain sources list to see if engine is back ✅ Zero side effects; existing config preserved ❌ If engine is permanently dead, retries forever; user must choose another option B) Switch to local PGLite (one-way — moves existing config to .bak) ✅ Fastest path to a working local engine if user has abandoned the old one ✅ ~30s; no accounts; private to this machine ❌ Destructive — existing config moved to ~/.gbrain/config.json.gstack-bak-{ts} C) Switch brain mode (continue to Step 2 path picker) ✅ Lets user pick Path 1/2/3/4 to re-init from scratch ✅ Preserves existing config until they explicitly init the new one ❌ Longer flow if user just wants to repair to PGLite D) Quit (do nothing) ✅ No cons — this is a hard-stop choice ❌ N/A Net: A is the right starting move; B/C are explicit destructive paths; D bails.

If A (Retry): re-run ~/.claude/skills/gstack/bin/gstack-gbrain-detect with GSTACK_DETECT_NO_CACHE=1 (busts the 60s cache). If the new gbrain_local_status is ok, continue to Step 2. If still broken-db or broken-config, fire the same AskUserQuestion again (the user picks again).

If B (Switch to PGLite) — execute the rollback-safe init sequence (plan D7):

BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
mv "$HOME/.gbrain/config.json" "$BACKUP"
# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — best for
# code retrieval. Without the key, fall back to gbrain's own auto-selected
# embedding provider chain (OpenAI 1536d when OPENAI_API_KEY is present, etc.).
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
set --  # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
if [ -n "${VOYAGE_API_KEY:-}" ]; then
  set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
fi
if ! gbrain init --pglite --json "$@"; then
  # Restore on failure
  mv "$BACKUP" "$HOME/.gbrain/config.json"
  echo "gbrain init failed. Your previous config was restored at $HOME/.gbrain/config.json." >&2
  echo "PGLite directory at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` if needed before retrying." >&2
  exit 1
fi
echo "Switched to local PGLite. Previous config saved at $BACKUP — review before deleting."

Then jump to Step 5a (MCP registration; the new PGLite engine is registered as local-stdio).

If C (Switch brain mode): continue to Step 2's normal path picker.

If D (Quit): STOP the skill cleanly.

sections/transcript-gate.md (verbatim)

<!-- AUTO-GENERATED from transcript-gate.md.tmpl — do not edit directly --> <!-- Regenerate: bun run gen:skill-docs -->

After memory sync is wired (Step 7) but before persisting the CLAUDE.md config (Step 8), offer to bring this Mac's coding-agent transcripts + curated ~/.gstack/ artifacts into gbrain so the retrieval surface (per-skill manifests, salience block) has data to surface.

Run the probe to size the operation:

bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe

Read the output. If Total files in window: 0, skip — there's nothing to ingest. Set gstack-config set transcript_ingest_mode incremental silently and continue to Step 8.

If New (never ingested) is < 200 AND total bytes are < 100MB: silent bulk via bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet. Set transcript_ingest_mode=incremental and continue.

Otherwise (the "many transcripts on disk" path): AskUserQuestion with the exact counts AND the value promise. Default scope is current repo only, last 90 days:

"Found <N_repo> transcripts in THIS repo (<repo-slug>) over the last 90 days, plus <N_other> across other repos on this machine (<bytes> total if all ingested). Ingest THIS repo's transcripts into gbrain?

What you get after this: every gstack skill auto-loads recent salience from your past sessions in this repo, so the agent finds your prior work without you describing it. You can query 'what was I doing on day X' and get a real answer. Per-session pages are searchable, taggable, and deletable. Secret scanning runs before any push.

What stays the same: nothing leaves your machine unless gbrain sync is enabled (Step 7). Per-repo trust policies still apply.

Multi-Mac note: if you HAVE enabled brain sync (Step 7), these transcript pages will sync across your Macs. Caveat: deleting a transcript page later removes it from gbrain but git history retains it in prior commits. Use gstack-transcript-prune to delete in bulk; use git filter-repo on the brain remote for hard-delete from history."

Options:

  • A) Yes — this repo, last 90 days (recommended; ~est min)
  • B) Yes — this repo, ALL history
  • C) Yes — this repo + other repos on this machine
  • D) Skip historical, track new from now (transcript_ingest_mode=incremental)
  • E) Never ingest transcripts (transcript_ingest_mode=off)

After answer:

~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <choice>
bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync

(--no-brain-sync because Step 7 already wired that path; this just runs the code import + memory ingest stages. Brain-sync will run on the next preamble hook.)

If A/D/E, ingest is incremental from this point on; preamble-boundary hook runs bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet on every skill start (cheap mtime fast-path).

Reference doc for users: setup-gbrain/memory.md (linked from CLAUDE.md Step 8).

Back to garrytan/gstack (Garry Tan's Claude Code skill suite) or Agent skills.