Extract duplicated implementations of startCallbackServer, parseRedirectUrl,
getUserEmail, and token refresh logic from google-antigravity.ts and
google-gemini-cli.ts into a shared google-oauth-utils.ts module.
On older Linux distributions (e.g., RHEL 8 with older glibc), the native
Rust addon fails to load. The proxy throws on every function call, but
wrapTextWithAnsi and visibleWidth in pi-tui had no JS fallback — causing
an uncaught crash during TUI rendering.
Fix: Both functions now catch native throws and fall back to JS
implementations (simple word-wrap and ANSI-strip length).
Fixes#1418
Added scripts/generate-openrouter-models.mjs that fetches the full model
list from OpenRouter's API and generates TypeScript entries matching the
existing models.generated.ts format. Run with:
node scripts/generate-openrouter-models.mjs > /tmp/openrouter.ts
Updated the OpenRouter section in models.generated.ts from 241 → 350
models, including all nvidia/nemotron variants requested in the issue.
Fixes#1407
* fix: sync worktree completion artifacts back to external state before merge (#1412)
When a worktree's .gsd/ was a real directory (not symlinked to external
state), milestone completion artifacts (SUMMARY, VALIDATION, updated
ROADMAP) were written locally but never synced back. The project root's
deriveState() read from external state and found no SUMMARY — reporting
the milestone as incomplete.
Changes:
- auto-worktree.ts: Added syncWorktreeStateBack() that copies milestone
and slice .md files from worktree .gsd/ to the main external state dir
- auto.ts: Call syncWorktreeStateBack() in tryMergeMilestone before the
git merge, ensuring artifacts are visible from the project root
Fixes#1412
* fix: emit agent_end after abort during tool execution (#1414)
When a user aborts a turn while a tool call is running, the abort RPC
succeeds but agent_end was never emitted. RPC consumers tracking turn
lifecycle via events got stuck in a 'streaming' state permanently.
Fix: After abort() + waitForIdle(), emit a synthetic agent_end if the
agent is no longer streaming. This ensures consumers always see the
turn-complete signal regardless of how the turn ended.
Fixes#1414
Two root causes for the false "Interrupted Session Detected" prompt
that appears every time /gsd is run after a normal exit:
1. guided-flow.ts showed the crash recovery menu even for bootstrap
crashes (unitType="starting", unitId="bootstrap", completedUnits=0)
where no work was lost. Now these are silently discarded — the menu
only appears when real auto-mode work was interrupted.
2. session-lock.ts exit handler cleaned the OS lock directory
(.gsd.lock/) but not the auto.lock metadata file. On next startup,
readCrashLock() found the stale file and triggered false recovery.
Now the exit handler also removes auto.lock.
smartStage() was excluding the entire .gsd/ directory from git staging,
which is correct when .gsd/ is symlinked to external state. But on
Windows (junction links) or projects where .gsd/ is git-tracked (not
gitignored), this caused a mid-milestone behavioral discontinuity:
1. One-time cleanup removes runtime files from the index
2. After cleanup, nativeAddAll() + nativeResetPaths('.gsd/') causes ALL
.gsd/ files to be unstaged — including milestone artifacts
3. autoCommit returns null (nothing staged) for the rest of the milestone
4. Work continues silently with no commits, no errors, no warnings
5. Worktree teardown loses all uncommitted .gsd/ artifacts
Fix: replace the blanket '.gsd/' exclusion with targeted RUNTIME_EXCLUSION_PATHS.
Milestone artifacts (.gsd/milestones/, preferences.md, DECISIONS.md, etc.)
are now committed normally when they're tracked. When .gsd/ is in .gitignore
(the default), git add -A already skips it — the reset is a harmless no-op.
Updated git-service.test.ts to verify the new behavior: runtime files
excluded, milestone artifacts committed.
Fixes#1326
When a GSD session crashes hard (SIGKILL, OOM, etc.) without running its
exit handler, the proper-lockfile OS lock directory (.gsd.lock/) is left
stranded. On the next /gsd auto resume, acquireSessionLock detects the dead
PID, cleans up the stale directory, and re-acquires via the retry path.
10 seconds later, proper-lockfile's update timer fires. Due to a subtle
interaction between the synchronous fs adapter (lockSync / toSyncOptions)
and the setTimeout boundary in Node.js v25+, the ECOMPROMISED error
propagates up through the synchronous callback chain and becomes an
uncaught exception — even though the onCompromised callback sets
_lockCompromised = true without throwing.
The _gsdEpipeGuard uncaughtException handler only handled EPIPE, so it
re-threw ECOMPROMISED, crashing the process. Each crash wrote a new
"interrupted session" record, causing an infinite crash loop on resume.
Two fixes:
1. index.ts: Handle ECOMPROMISED in _gsdEpipeGuard. Exit with code 1
(non-zero to signal failure) so the process.once("exit") handler runs
and removes the lock directory, allowing the next session to start clean.
2. session-lock.ts: The retry path's onCompromised was missing
`_releaseFunction = null`, unlike the primary path. This left the
release function pointer live after compromise, causing validateSessionLock
to return true and preventing graceful stop detection. Now matches primary.
10 tests that run against the installed gsd binary after npm publish:
1. headless query returns valid JSON
2. Empty project → pre-planning phase
3. Milestone with roadmap → planning phase
4. All tasks done → summarizing phase
5. Complete milestone → complete phase
6. Stale auto.lock doesn't block --version
7. Crash recovery query works with stale lock
8. Non-TTY exits quickly with clean error
9. Version skew detected before TTY check
10. --help works (native addon loads or falls back)
Wired into pipeline.yml test-verify job after fixture tests
and before @next promotion.
These catch the state machine / infrastructure bugs from #1308
that unit tests can't reach — they exercise deriveState through
the real gsd binary with real .gsd/ directory structures.
Part of #1308
Replaces 30+ inline `unitId.split("/")` + destructuring patterns across
16 production files with a single `parseUnitId()` helper that returns
`{ milestone, slice?, task? }`. If the unit ID format ever changes,
only one function needs updating.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The retry lock acquisition path (from stale lock recovery in #1251)
used a 5-minute stale threshold and no onCompromised handler, while
the primary path used 30 minutes and a graceful flag-based handler.
This mismatch meant locks acquired via the retry path would throw
ECOMPROMISED (uncaught, crashes process) if the event loop stalled
for >5 minutes — which happens during long LLM operations.
Fixed:
- Stale timeout: 300_000 → 1_800_000 (matches primary)
- Added onCompromised handler (sets _lockCompromised flag)
- Added process.on('exit') safety net (matches primary)
Also: reporter is on Node v25.6.1 which is unsupported — GSD requires
Node >=22.0.0 with 24 LTS recommended.
Fixes#1304
makeTreeWritable used statSync which follows symlinks. On NixOS and
nix-darwin, ~/.gsd/agent/bin/ contains symlinks to the immutable Nix
store (/run/current-system/sw/bin/). Attempting to chmod those targets
crashed GSD on startup with EPERM.
Changes:
- Use lstatSync instead of statSync — detects symlinks without
following them
- Skip symlinks entirely (they don't carry own permissions, targets
may be immutable)
- Added try/catch around chmodSync as safety net for any remaining
permission errors on unusual filesystems
Secondary analysis: rmSync with force:true already handles symlinks
correctly (removes the link, not the target). cpSync with force:true
replaces symlinks with regular files (desired behavior for resource
sync).
Fixes#1298
Consolidate the repeated `err instanceof Error ? err.message : String(err)`
pattern into a single `getErrorMessage(err)` utility. Reduces visual noise in
catch blocks across 20 files in the GSD extension.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: consolidate DB-fallback inline functions in auto-prompts
Extract shared inlineFromDbOrFile() helper that encapsulates the
repeated pattern of checking DB availability, dynamically importing
context-store, running a query, formatting results, and falling back
to the filesystem. The three public functions (inlineDecisionsFromDb,
inlineRequirementsFromDb, inlineProjectFromDb) become thin wrappers
that pass only the differing query/format logic as a callback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update source-level test to match refactored DB-fallback function name
The context-compression test greps auto-prompts.ts source for
`inlineGsdRootFile(base, "project.md"` which was replaced by
`inlineProjectFromDb(base)` in the consolidation refactor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(gsd): skip non-artifact UAT dispatch in auto-mode
Non-artifact-driven UATs (human-experience, live-runtime, mixed) were
dispatched only to write a "surfaced-for-human-review" verdict, which
then blocked the verdict gate and killed auto-mode progression. Auto
now only dispatches artifact-driven UATs it can actually execute.
- checkNeedsRunUat returns null for non-artifact-driven UAT types
- Remove pauseAfterDispatch flag (always artifact-driven now)
- Strip human-review template path from run-uat prompt
- Remove dead pause-after-UAT logic from auto.ts
- Add test for non-artifact UAT skip + stale replay guard
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update buildRunUatPrompt call in direct dispatch after signature change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add onCompromised handler to prevent uncaught throw in setTimeout
- Increase stale threshold from 5min to 30min for laptop sleep safety
- Release OS lock explicitly in SIGTERM handler
The test asserted that captureIntegrationBranch commits metadata to git,
but #1258 intentionally stopped committing .gsd/ artifacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>