Skip to content

fix: two silent gbrain failures - DNS-isolated envs and dot-leading slugs - #2884

Open
Infiniteyieldai wants to merge 2 commits into
garrytan:mainfrom
Infiniteyieldai:fix/gbrain-dns-isolation-and-dot-slug-ingest
Open

Infiniteyieldai wants to merge 2 commits into
garrytan:mainfrom
Infiniteyieldai:fix/gbrain-dns-isolation-and-dot-slug-ingest

Conversation

@Infiniteyieldai

@Infiniteyieldai Infiniteyieldai commented Sep 16, 2026

Copy link
Copy Markdown

Why (in my own words)

Two bugs that both bite silently, found while running /setup-gbrain on a machine whose brain is remote Postgres.

1. A sandboxed or offline environment gets told its brain is broken. gbrain sources list fails with Cannot connect to database: getaddrinfo ENOTFOUND whenever the environment has no DNS at all: a coding agent's sandbox, a restricted CI runner, a laptop on a plane. The classifier read that as broken-db, which is the one verdict that routes /setup-gbrain into Step 1.5 remediation, and Step 1.5 offers to move your working ~/.gbrain/config.json aside and re-init the engine. The brain is fine. It is simply not reachable from inside that sandbox. Offering a destructive repair for a network boundary is the wrong answer to the wrong question.

2. A dot-leading project slug wedges memory ingest permanently. gbrain's import walker prunes any path segment starting with a dot, and stagedRelPath() maps a page slug 1:1 onto its path in the staging dir. So a project slug of .claude, which gstack mints whenever a session runs with cwd ~/.claude, stages a file gbrain never collects. The staged-vs-collected reconciliation mismatches and the run refuses to advance state. That refusal is correct, but the consequence is that the same pages re-stage and fail on every subsequent run. Transcript ingest never recovers on its own. Mine was stuck on 8 transcripts until I traced it.

Live evidence

Bug 2: ingest wedged, then fixed. Same command, same real ~/.gstack, same cwd ($HOME), only the checkout differs. A fixture project dir ~/.gstack/projects/.prfixture/ supplies the dot-leading slug.

=== BEFORE - upstream a6b3a575 (unpatched), run from $HOME ===
[memory-ingest] ERR: gbrain import accounted for 14 of 15 staged page(s) (imported=1, unchanged=13).
gbrain collected 14 file(s) from the staging dir. Refusing to advance state - the unaccounted pages
would be marked ingested without landing in the brain.

Ingest pass complete (bulk):
  written:               0
  failed:                15
  duration:              25.1s

=== AFTER - this branch (patched), run from $HOME ===
Ingest pass complete (bulk):
  written:               15
  failed:                0
  duration:              34.9s

Bug 1: same sandbox, same machine, different verdict.

=== BEFORE - upstream a6b3a575, inside a no-DNS sandbox ===
  "gbrain_engine": "postgres",
  "gbrain_local_status": "broken-db",       <- Step 1.5 offers to move config.json aside

=== AFTER - this branch, inside the same no-DNS sandbox ===
  "gbrain_engine": "postgres",
  "gbrain_local_status": "network-isolated", <- local stages skip, no remediation offered

The discrimination holds in both directions, verified on this branch with real network access:

=== branch, WITH network (healthy brain) ===
  "gbrain_local_status": "ok"

=== branch, WITH network, genuinely unresolvable host ===
  GBRAIN_DATABASE_URL=postgresql://u:p@nope-gstack-pr.invalid:5432/db
  "gbrain_local_status": "broken-db"        <- real bad host still reaches remediation

And the sync orchestrator now skips with an accurate reason instead of pointing at a timeout knob that cannot help:

SKIP  code    skipped - local engine network-isolated - no DNS in this environment (sandbox,
              restricted CI, or offline), so the engine is unreachable from here and its real
              health is unknown; re-run with network access - raising
              GSTACK_GBRAIN_PROBE_TIMEOUT_MS will not help (1.9s)
SKIP  memory  skipped - local engine network-isolated - ...
OK    brain-sync   curated artifacts pushed (0.2s)

Tests. New regression file passes on this branch and cannot even link against base (safeSlugSegment does not exist there):

$ bun test test/regression-dot-leading-slug.test.ts
 5 pass  0 fail  11 expect() calls

Full affected surface (test/*gbrain*, test/*memory-ingest*, test/*transcript*, test/code-intelligence*, new file):

 728 pass  9 skip  1 fail  2209 expect() calls  (49 files)

The single failure is gstack-memory-ingest.test.ts > #2394 ... parity, a 5s timeout. It is pre-existing, not from this change: it fails the same way on a clean a6b3a575 worktree with no patches applied, and is in fact slower there (15.9s vs 6.7s on this branch).

Scope

  • Changed:
    • lib/gbrain-local-status.ts: new network-isolated status, DNS-shape detection, memoized control lookup gated behind GSTACK_ASSUME_NO_DNS, and the status added to the existing #2520 bearer-token thin-client reclassification so a remote-only brain is not mislabelled when local DNS is dead.
    • bin/gstack-gbrain-sync.ts: entry in the exhaustive reason map. The three stage call sites special-case only timeout to proceed, so the new status falls through to the normal skip with no other edit.
    • bin/gstack-gbrain-detect: --is-ok whitelist plus the documented enum, so brain-aware blocks keep rendering in a sandbox.
    • bin/gstack-memory-ingest.ts: safeSlugSegment() applied at slug-build time in repoSlug and buildArtifactPage; stagedRelPath now throws on a dot-leading segment; buildArtifactPage exported for the test.
    • setup-gbrain/SKILL.md.tmpl, sync-gbrain/SKILL.md.tmpl plus regenerated SKILL.md via bun run gen:skill-docs.
    • Tests: 3 new cases in test/gbrain-local-status.test.ts (via a new dns-failed fake behaviour), new test/regression-dot-leading-slug.test.ts.
  • Verified live by: running both bugs to failure on a clean a6b3a575 worktree and to success on this branch, on a real brain (remote Postgres, 1148 pages), inside and outside a no-DNS sandbox. Outputs pasted above.
  • Did NOT test: PGLite engines (this machine is Postgres; PGLite cannot emit a DNS error, and both PGLite branches gate on configuredEngine() === "pglite" anyway). Windows. The remote-http ingest branch, which is why the stagedRelPath guard throws rather than silently sanitizing. An egress proxy that resolves names but refuses connections is explicitly out of scope: it surfaces as ECONNREFUSED, not a DNS error, and still classifies broken-db.

Design note on why network-isolated is a new status rather than reusing timeout

timeout looked like the obvious fit and is wrong. It is the one non-ok status the sync orchestrator special-cases to PROCEED (#1964), so reusing it sends code, memory and dream into a full walk against an unreachable DB, and code-intelligence/picker.ts would advertise gbrain as available in an environment where it cannot work. A distinct status falls through to the normal skip, and the exhaustive Record<Exclude<LocalEngineStatus, "ok">, string> in the orchestrator makes the compiler enumerate every consumer that needs a decision.

Liveness proof

Screenshot attached by the human author (@Infiniteyieldai) in a follow-up comment on this PR.

Checklist

  • Liveness screenshot attached (GSTACK PR typed live into a real surface) or PR author is @garrytan (owner exemption) — attaching now, see comment
  • This is not a generated-file-only diff (I edited the source/template and regenerated)
  • No ETHOS.md edits, and no changes to voice / founder perspective / YC references
  • New public command / external service / host adapter has an accepted issue linked (or N/A) — N/A, no new public command or external service; GSTACK_ASSUME_NO_DNS is a test seam and manual escape hatch on an existing code path
  • Linked issue or reproduction: no existing issue found. Both bugs are reproduced from a clean a6b3a575 worktree in the Live evidence section above. Happy to file tracking issues if you would prefer them separated.

Infiniteyieldai and others added 2 commits September 17, 2026 06:41
`gbrain sources list` fails with "Cannot connect to database: getaddrinfo
ENOTFOUND" whenever the environment has no DNS at all: an agent sandbox, a
restricted CI runner, an offline laptop. The classifier read that as
broken-db, so /setup-gbrain Step 1.5 offered to move a perfectly healthy
config.json aside and re-init the engine.

Add a `network-isolated` status. On the failure path only, a control lookup
decides: if a well-known public name does not resolve either, DNS is dead
here and the engine's real state is unknowable, so report network-isolated;
if it does resolve, the failure is specific to the configured host and the
broken-db verdict stands.

network-isolated is deliberately NOT timeout. The three sync stages special
-case timeout to PROCEED (garrytan#1964), so reusing it would send code, memory and
dream into a full walk against an unreachable DB and tell the user to raise
GSTACK_GBRAIN_PROBE_TIMEOUT_MS, which cannot help. It falls through to the
normal skip instead, while `--is-ok` still passes so brain-aware blocks
keep rendering.

GSTACK_ASSUME_NO_DNS=1|0 forces the probe either way, so the tests cover
this without a network dependency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gbrain's import walker prunes any path segment beginning with a dot, and
stagedRelPath() maps a page slug 1:1 onto its path in the staging dir. So a
project slug of ".claude" (minted whenever a session runs with cwd
~/.claude) staged a file gbrain never collected. The staged-vs-collected
reconciliation then mismatched and the run refused to advance state -
correctly, but that meant the SAME pages re-staged and failed on EVERY
subsequent run. Transcript ingest never recovered on its own.

Sanitize the leading dot to "dot-" at slug-build time, not in
stagedRelPath: the staged path, the page slug and the slug recorded in
state must stay identical, which both the reconciliation map and the
resume-path reconstruction rely on.

stagedRelPath now throws on a dot-leading segment as defence in depth,
since disambiguateSlugs can re-pin a slug straight from state and the
remote-http branch records page_slug without gbrain reconciliation. A loud
failure beats a silent prune that wedges the next run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Sep 16, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@trunk-io

trunk-io Bot commented Sep 16, 2026

Copy link
Copy Markdown

An error occurred while submitting your PR to the queue: Only users that are a part of this repo's Trunk organization or have write permissions to the repo can submit a PR to the queue

@Infiniteyieldai

Copy link
Copy Markdown
Author
image Liveness proof for #2884

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant