Two related fixes for `gsd --mode mcp` that the audit missed on first pass:
1. Tool inventory — session.agent.state.tools was the *active* subset, not
the full registry. Before this change, MCP clients connected to GSD saw
63 tools and four built-ins were silently missing: `find`, `grep`, `ls`,
and `hashline_edit`. After: 67 tools, matching the full _toolRegistry.
Fix: call session.getAllTools() + session.setActiveToolsByName() before
starting the MCP transport so every registered tool is active for the
lifetime of the MCP session.
2. SDK subpath resolution — the #3603 createRequire workaround no longer
works with @modelcontextprotocol/sdk 1.27.x + current Node. The
wildcard export ./* → ./dist/cjs/* does NOT auto-append `.js`, and
_require.resolve fails with "Cannot find module .../server/stdio".
End-to-end handshake was actually broken in src/mcp-server.ts even
before my earlier F5 change. Fix: use explicit `.js` suffixes on
every subpath import (server/index.js, server/stdio.js, types.js),
matching the convention already in use by packages/mcp-server/.
The regression test is rewritten to enforce the `.js`-suffix convention
and reject any bare subpath or lingering createRequire resolution.
Verified end-to-end via raw JSON-RPC against `gsd --mode mcp --bare`:
BEFORE_COUNT=63
AFTER_COUNT=67
diff: +find +grep +hashline_edit +ls
Test sweep: 76 tests pass across mcp-createRequire, stream-adapter,
mcp-server, workflow-tools.
https://claude.ai/code/session_0174sYny3VvdwYTdCNTmY4Do
Rename intermediateToolCalls → intermediateToolBlocks to match upstream
rename, and pass onElicitation via extraOptions (4th arg) instead of
overrides (3rd arg) in buildSdkOptions test.
The lockfile was still pointing at 2.66.1 after the v2.68.0 release;
`npm install` during the audit work resynced it. No dependency graph
changes, just version-string alignment.
https://claude.ai/code/session_0174sYny3VvdwYTdCNTmY4Do
Audit-driven fixes across the two MCP server surfaces and the Claude Code
streaming adapter:
- src/mcp-server.ts: propagate `extra.signal` into `tool.execute` so MCP
clients can actually cancel long-running Bash/WebFetch/grep calls, and
route the remaining `/server` subpath import through `createRequire`
for #3603 consistency.
- src/tests/mcp-createRequire.test.ts: extend regression coverage to the
`/server` subpath.
- claude-code-cli/stream-adapter.ts: (a) classify aborts as `aborted`
instead of the retry-eligible `stream_exhausted_without_result`,
(b) merge final-turn toolCall blocks from the builder into the
AssistantMessage via the new `mergePendingToolCalls` helper so a turn
ending in `tool_use` stop_reason no longer drops its tool calls, and
(c) resolve the SDK permission mode via `resolveClaudePermissionMode`
(auto-mode → bypass, interactive → acceptEdits, env override).
- packages/mcp-server/src/server.ts: make `gsd_query` actually respect
its `query` argument with known categories + forward-compatible
fallback, and thread `extra.signal` into `gsd_execute` so an aborted
RPC request cancels the newly-created session instead of leaking a
background RpcClient process.
- stream-adapter test suite: add regression tests for abort
classification, final-turn tool-call merging, and permission mode
resolution.
Verified via: mcp-createRequire, stream-adapter (27), partial-builder,
mcp-server package (31), workflow-tools (13) — 83 tests green.
https://claude.ai/code/session_0174sYny3VvdwYTdCNTmY4Do
Providers like claude-code, openai-codex, google-gemini-cli use external
CLI auth — they don't need API keys. The doctor was incorrectly reporting
"claude-code key missing" for subscription users.
- Replace notification overlay 3s polling with onNotificationStoreChange
subscription for immediate updates; keep 30s safety-net for cross-process
- Remove Ctrl+Shift+P parallel fallback that conflicts with cycleModelBackward
- Add hasFallback flag to GSDShortcutDef so hint text is accurate
- Fix misleading _withLock comment; rename ownsLock → createdLock
Closesgsd-build/gsd-2#4076
Filter models whose provider has no working API key or OAuth out of
every user-facing selection path. Previously, stale defaults and scoped
sets could leak unconfigured models into /model, /gsd model, and auto
run — the user could "pick" a model that immediately threw on use.
- model-selector: filter scopedModels via isProviderRequestReady;
default to "all" scope when no scoped model is ready.
- model-controller: same filter for getModelCandidates, so exact-match
resolution from /model <term> can't return an unauth'd scoped model.
- model-resolver: gate findInitialModel step 3 on provider readiness so
a stale saved default falls through to the available-models path.
- startup-model-validation: check configuredExists against getAvailable
instead of getAll, so a configured-but-unauth default triggers the
fallback picker and thinking-level reset.
- auto-start: validate resolveDefaultSessionModel against the live
registry + auth before snapshotting, and warn when PREFERENCES.md
names an unconfigured model.
https://claude.ai/code/session_015q6b23ap9Pyqdogzz2FXGh
- auth-storage.test.ts: 8 tests for getEarliestBackoffExpiry()
- sdk.test.ts: 12 tests for CredentialCooldownError class
- infra-errors-cooldown.test.ts: 35 tests for isTransientCooldownError(),
getCooldownRetryAfterMs(), and exported constants
Required by CI lint (require-tests.sh) per CONTRIBUTING.md.
Closes#4052
getApiKey() retry loop (3 attempts, ~12s) couldn't outlast the 30s
rate-limit backoff window, causing cooldown errors to cascade through
the auto-loop and trigger a hard stop after 3 consecutive failures.
- Add AuthStorage.getEarliestBackoffExpiry() to expose when the next
credential becomes available
- Update getApiKey() to sleep until backoff expiry (up to 60s) instead
of fixed 2s/4s/6s delays
- Add isTransientCooldownError() detector in infra-errors.ts
- Auto-loop now waits 35s on cooldown errors without incrementing the
consecutive error counter
Closes#4052
PR #3564 narrowed the internal overlay to @gsd* prefixes only, which
dropped non-hoisted optional deps like @anthropic-ai/claude-agent-sdk
from the merged ~/.gsd/agent/node_modules directory. Revert to overlaying
all non-dotfile internal entries so optional deps resolve correctly.
- Use content fingerprint (packageRoot + sorted entry names from both
dirs) in .gsd-merged marker so pnpm add/remove triggers rebuild
- Restrict overlay loop to @gsd* scopes only, preventing accidental
shadowing of hoisted deps with internal versions
- Guard marker write behind linkedCount > 0 to avoid stamping success
on a broken/empty merged directory
- Log warnings when readdirSync fails on hoisted/internal roots
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pnpm's virtual-store layout doesn't hoist @gsd/* workspace scopes to
the parent node_modules, so the simple symlink-to-hoisted approach from
the original fix (#3529) left workspace packages unresolvable.
Detect when workspace scopes are missing from the hoisted root and
create a real node_modules directory with symlinks from both the hoisted
root (external deps) and internal root (workspace packages). A .gsd-merged
marker file skips rebuild on subsequent startups.
Restores behavioral tests deleted in the original PR and adds unit tests
for the pnpm merge path and scope detection logic.
Reported-by: @moekify
Fixes: #3564 (comment)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>