Skip to content

fix: CLI parity — runtime account selection, live reasoning, watcher restart loop - #177

Merged
devswha merged 5 commits into
mainfrom
fix/cli-parity-credential-thinking-watcher
Sep 25, 2026
Merged

devswha merged 5 commits into
mainfrom
fix/cli-parity-credential-thinking-watcher

Conversation

@devswha

@devswha devswha commented Sep 25, 2026

Copy link
Copy Markdown
Owner

An adversarial pass comparing the app with gjc -p on SDK 0.17.6. The same prompts ran through the app's public WebSocket (chat.send, which is how the browser sends a turn) and through the CLI, in one fixture repo reset between runs. The pass found four defects, fixed here.

Fixes

1. The runtime now chooses the account, as it does in the CLI (fix(gjc), blocker)
When a provider had several stored rows, the adapter pinned the lowest row id. For Anthropic that row belongs to an organization that refuses OAuth, so every app turn failed with 403 oauth_not_allowed_for_organization. The CLI answered from another account because the runtime's own selection honours gjc accounts pin, skips accounts its routing has excluded and rotates when one hits a usage limit. A pinned selector disabled all three. Only an explicit credentialId installs a selector now, and the run reports the row the runtime chose (getSessionCredentialRowId). Contract tests assert that no selector is installed and that the reported row is the runtime's choice, not the lowest id.

2. Reasoning streams while the model thinks (feat(chat))
The CLI streams reasoning deltas. The app sent reasoning only at thinking_end, so a turn looked frozen for the whole reasoning phase (16 s in one measured turn). thinking_delta frames now fill one live __thinking_<session> row, painted at most once per frame. The thinking record replaces it, and complete clears one that never got its record. Tested at two levels: the event mapper, and a DOM test against the real store. Checked in a browser: under the Detailed density, "Reasoning…" fills in while the turn runs. Balanced and Compact still hide reasoning by design.

3. The native session watcher no longer restarts on every transcript write (fix(core))
macOS FSEvents reports Modify(Metadata) on the session-scope directory for each atomic transcript write. The watcher treated that as a new directory and rescanned it. A 25k-entry scope exceeded the 4096-entry backfill bound, so the watcher failed, restarted and ran a full reconciliation. It did this 16 times in a few minutes of npm run dev. The cause was pinned down with an instrumented binary; only created or renamed directories are backfilled now. A regression test covers a metadata event on a directory above the bound. After the fix the probe survives live CLI writes.

4. Tests no longer write into the operator's crash journal (test)
Bun test files wrote their SDK handled errors into ~/.gjc, where they read as app crashes. 8 of the 12 records after the 0.17.6 upgrade came from this suite. Each Bun test file now runs with a throwaway HOME, and GIT_CONFIG_GLOBAL stays set so commit fixtures still work. Zero records were written during the full npm run verify below.

Parity confirmed

Checked through both paths:

  • An edit plus a bash call produces the same result and the same diff.
  • The project AGENTS.md rules are applied, and the skill list is identical.
  • User-scope MCP servers load; there are none on this machine.
  • task/subagent delegation works.
  • Large bash output spills to a file at the same 8 MiB limit.

Checked on the app side:

  • Multi-turn memory and resume work.
  • Aborting mid-bash leaves no orphan process.
  • A steer sent mid-run reaches the running turn.
  • Three concurrent sessions each ran a 10 s sleep; together they took 24 s of wall time.
  • /fast, /effort, /context, /usage and /tools all work in the app. gjc -p does not run slash commands.

Latency for the same one-word turn: app 11.6–11.9 s, CLI 13.5–13.8 s (the CLI pays process start).

The service tier follows the runtime, as it does in the CLI (#160). A new session reports no tier; after /fast on the next turn reports priority; after /fast off the tier is gone. The app pins none. The spec records this.

Known differences, left as they are

  • Withheld tools are listed with reasons in server/gjc-agent-tools.ts: python/eval, job/monitor, github, debug, checkpoint/rewind, tool discovery and move_session. A project's own .gjc/mcp.json and extension modules do not load.
  • Pre-existing: ChatInterface calls sessionStore.setActiveSession during render, which logs a React warning on every session open. Zero-delay synthetic typing after a reload trips "Maximum update depth" in the composer resize; it does not reproduce at a 10 ms keystroke delay.

Verification

  • npm run verify: exit 0. This covers audit, licenses, typecheck, check:core (69 Rust tests), the full Node and Bun suites, the 8 GJC e2e tests, lint, identity and build.
  • Live runs against the dev stack for every item above.

Refs #158 #160 #162

With several stored rows for a provider the adapter pinned the lowest
row id. That bypassed gjc accounts pin, routing exclusions and
usage-limit rotation, and sent every Anthropic turn to an account whose
organization refuses OAuth (403) while the CLI answered from another.
Only an explicit credentialId installs a selector now; the run reports
the row the runtime settled on.
Reasoning reached the browser as one block at thinking_end, so a long
reasoning phase looked frozen while the terminal streamed it.
thinking_delta previews now fill one live row per session, painted once
a frame; the thinking record replaces it, and the end of the turn
clears one that never got its record.
macOS reports a metadata change on the session-scope directory for
every atomic transcript write. The watcher rescanned that directory
each time; a scope with 25k entries overflowed the 4096-entry bound and
restarted the watcher into a full reconciliation over and over.
SDK sessions in the Bun suites wrote their handled errors into ~/.gjc,
where they read as app crashes (8 of 12 post-0.17.6 records). Each Bun
test file now runs with a throwaway HOME; git keeps the operator's
global config.
Account selection and the service tier follow the runtime, as in the
CLI; the handoff records the parity matrix, the fixes, the remaining
gaps and the beta.20 blocker.
@devswha
devswha merged commit 74faa35 into main Sep 25, 2026
6 checks passed
@devswha
devswha deleted the fix/cli-parity-credential-thinking-watcher branch September 25, 2026 12:37
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