Skip to content

fix(cli): stop thin-client jobs/config from fabricating a scratch PGLite (#2949) - #2951

Merged
time-attack merged 1 commit into
garrytan:masterfrom
ss251:fix/thin-client-jobs-scratch-db
Jul 20, 2026
Merged

fix(cli): stop thin-client jobs/config from fabricating a scratch PGLite (#2949)#2951
time-attack merged 1 commit into
garrytan:masterfrom
ss251:fix/thin-client-jobs-scratch-db

Conversation

@ss251

@ss251 ss251 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #2949.

Problem

jobs list|get have had remote MCP routing since v0.32, but the CLI shell still ran connectEngine() before dispatch. On a thin-client install that fabricates an empty scratch PGLite in the thin-client GBRAIN_HOME and replays the entire migration chain (Schema version 1 → 122, 117 migration(s) pending…) on every invocation, before the remote call even runs. The host-only jobs subcommands (work, supervisor, submit, stats, …) and config did the same instead of refusing — jobs stats happily reports an empty queue for a busy host, and config set writes rows nobody will ever read (the v0.31.1 silent-empty-results bug class).

Change

Follows the existing dispatch patterns:

  • src/cli.ts: thin-client jobs list|get dispatch engine-free (runJobs(null, args)) before connectEngine(); every other jobs subcommand refuses via refuseThinClient with a pinpoint hint (naming the routable subcommands and the submit_job/list_jobs/get_job MCP tools); config joins THIN_CLIENT_REFUSED_COMMANDS with a hint pointing at the host's file-plane config. jobs is deliberately NOT in the refused set — the partial dispatch owns it (noted in a comment at the set).
  • src/commands/jobs.ts: runJobs accepts BrainEngine | null, guarded at entry so null can only reach the MCP-routed list/get branches (which never touch the engine); narrowed once so the host-only cases typecheck unchanged.
  • Tests: behavioral additions to cli-dispatch-thin-client.test.ts (refusals carry hints + exit 1; jobs get/jobs list create no scratch store and print no migration replay) and source-audit pins in thin-client-routing-audit.test.ts (the new dispatch block, the config entry + hint, jobs's deliberate absence from the set, the null-engine guard).

Verification

  • bun test test/thin-client-routing-audit.test.ts test/cli-dispatch-thin-client.test.ts test/init-mcp-only.test.ts test/thin-client-upgrade-prompt.test.ts: 107 pass / 0 fail.
  • bunx tsc --noEmit: clean.
  • Full bun run test on this branch: 6924 pass; the only failures (hybrid-meta expansion, worker-registry) are environment-dependent on my machine (live ~/.gbrain, OPENAI_API_KEY present) and fail identically on an untouched master checkout.
  • Field-verified against a live v0.42.61.0 thin-client setup: this is the install where every jobs get printed the 117-migration replay above.

Related: #2948 / #2950 (same debugging session — remote ping polling the wrong field).

🤖 Generated with Claude Code

`jobs list|get` have had remote MCP routing since v0.32, but the CLI
shell still ran connectEngine() before dispatch — on a thin-client
install that fabricates an empty scratch PGLite in the thin-client
GBRAIN_HOME and replays the entire migration chain on every invocation,
before the remote call even runs. Host-only jobs subcommands (work,
supervisor, submit, ...) and `config` did the same instead of refusing.

- cli.ts: dispatch thin-client `jobs list|get` engine-free
  (runJobs(null, ...)); refuse the other jobs subcommands with a
  pinpoint hint; add `config` to THIN_CLIENT_REFUSED_COMMANDS with a
  hint (it reads/writes the host brain's config plane).
- jobs.ts: widen runJobs to accept a null engine, guarded so null can
  only reach the MCP-routed list/get branches.
- tests: behavioral (no scratch store created, no migration replay,
  refusals carry hints) + source-audit pins in the existing idioms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@time-attack
time-attack merged commit f1031d5 into garrytan:master Jul 20, 2026
22 checks passed
ss251 added a commit to ss251/gbrain that referenced this pull request Jul 21, 2026
…arrytan#3026)

The thin-client branches receive MinionJob rows as parsed JSON off the
MCP wire — every timestamp an ISO string — while formatJob /
formatJobDetail and the stalled-detection comparison hold a Date
contract (locally hydrated by MinionQueue.rowToJob). `jobs get <id>` on
a thin client crashed with "job.started_at.toISOString is not a
function" the moment the remote routing actually worked (unmasked by
the garrytan#2951 scratch-engine fix).

Rehydrate once at the unpack boundary via an exported helper that
coerces valid ISO strings to Dates, leaves Dates/nulls/malformed
strings untouched, and preserves the input type. Unit tests +
source-audit pins for both unpack sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hungcuong9125 pushed a commit to hungcuong9125/gbrain that referenced this pull request Jul 24, 2026
…ite (garrytan#2951)

`jobs list|get` have had remote MCP routing since v0.32, but the CLI
shell still ran connectEngine() before dispatch — on a thin-client
install that fabricates an empty scratch PGLite in the thin-client
GBRAIN_HOME and replays the entire migration chain on every invocation,
before the remote call even runs. Host-only jobs subcommands (work,
supervisor, submit, ...) and `config` did the same instead of refusing.

- cli.ts: dispatch thin-client `jobs list|get` engine-free
  (runJobs(null, ...)); refuse the other jobs subcommands with a
  pinpoint hint; add `config` to THIN_CLIENT_REFUSED_COMMANDS with a
  hint (it reads/writes the host brain's config plane).
- jobs.ts: widen runJobs to accept a null engine, guarded so null can
  only reach the MCP-routed list/get branches.
- tests: behavioral (no scratch store created, no migration replay,
  refusals carry hints) + source-audit pins in the existing idioms.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
time-attack pushed a commit that referenced this pull request Jul 28, 2026
…3026) (#3027)

The thin-client branches receive MinionJob rows as parsed JSON off the
MCP wire — every timestamp an ISO string — while formatJob /
formatJobDetail and the stalled-detection comparison hold a Date
contract (locally hydrated by MinionQueue.rowToJob). `jobs get <id>` on
a thin client crashed with "job.started_at.toISOString is not a
function" the moment the remote routing actually worked (unmasked by
the #2951 scratch-engine fix).

Rehydrate once at the unpack boundary via an exported helper that
coerces valid ISO strings to Dates, leaves Dates/nulls/malformed
strings untouched, and preserves the input type. Unit tests +
source-audit pins for both unpack sites.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

Thin-client jobs and config fabricate a scratch local PGLite (full migration replay on every invocation) instead of routing or refusing

2 participants