On first launch (before ~/.gsd/ exists), loader.ts prints a branded
ASCII logo and welcome message. Later, cli.ts unconditionally calls
printWelcomeScreen(), resulting in a duplicate banner.
Set GSD_FIRST_RUN_BANNER env flag in loader.ts after printing the
first-run banner. cli.ts now checks for this flag and skips the
welcome screen when it is already set.
The session-restart banner in register-hooks.ts is unaffected because
it only fires on non-first sessions (isFirstSession guard).
Closes#2245
The symlink test used single quotes in a commit message
(`-m 'add gitignore'`) inside a `&&`-chained shell command. On Windows,
`cmd.exe` doesn't treat single quotes as string delimiters, so git
received a mangled pathspec `gitignore'`. Split into two separate `run()`
calls with double-quoted commit message, matching every other test in
the file.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When await_job consumed async job results, onJobComplete still fired
follow-up messages for each job. Each follow-up triggered a wasteful
LLM turn where the agent could only say "Already captured...".
Add an `awaited` flag to Job. await_job sets it on all watched jobs
before waiting (avoiding a race with the promise .then() callback).
onJobComplete skips follow-up delivery for awaited jobs. Fire-and-forget
jobs still get follow-up messages as before.
Closes#2248
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
smartStage() was using git hash-object + update-index to bypass .gitignore
and force-stage .gsd/milestones/ files when .gsd is a symlink. This
contradicts the external state design (symlink = state lives outside repo)
and the documented deprecation of commit_docs.
Remove the force-add block, finish the commit_docs deprecation in
auto-prompts (always emit "do not commit"), and clean up the commitDocs
parameter from all call sites. The deprecation warning in
preferences-validation remains so users are told to remove the setting.
Closes#2247
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These files were being force-staged through the symlink by
_forceAddMilestoneArtifacts() bypassing .gitignore. External state
projects should not have .gsd/ in version control.
Remove the blanket loop that auto-activated every visible skill whose
name/description substring-matched tokens from extraContext and
taskPlanContent. This caused 32+ irrelevant skills (xcode-build,
ableton-lom, etc.) to load every auto-mode turn.
Skill activation now uses only explicit preference sources:
always_use_skills, skill_rules, prefer_skills, and skills_used from
task plan frontmatter.
Closes#2239
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes#2083
When an OpenRouter API key is stored in auth.json as type:"oauth" (instead
of type:"api_key"), getApiKey() calls getOAuthProvider("openrouter") which
returns undefined — OpenRouter is not a registered OAuth provider. Previously,
resolveCredentialApiKey returned undefined and getApiKey returned that directly,
never reaching the env-var or fallback-resolver paths.
Now, when resolveCredentialApiKey returns undefined, getApiKey falls through
to OPENROUTER_API_KEY env var and the fallback resolver instead of silently
failing with "Authentication failed."
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Skip jiti JIT compilation for bundled extensions that have pre-compiled .js
siblings, enable V8 bytecode caching on Node 22+, and batch directory
discovery to reduce syscalls during resource loading.
Fixes#2108
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix three sources of unbounded memory growth in the LSP client:
1. Message buffer: Add a 10 MB cap on client.messageBuffer. If an LSP
server sends incomplete or malformed data that causes the buffer to
exceed this limit, the buffer is discarded and reset to prevent
runaway memory usage.
2. Client/lock map eviction: clientLocks and fileOperationLocks entries
were never removed when a client was shut down via shutdownClient().
Now both maps are cleaned up alongside the clients map on shutdown.
3. Idle checker lifecycle: The idle check interval now stops itself when
no clients remain, and shutdownAll() explicitly stops it and clears
all global maps (clients, clientLocks, fileOperationLocks).
macOS APFS silently renames `.gsd` to `.gsd 2`, `.gsd 3`, etc. when a
directory already exists at the symlink target path. This causes GSD to
lose its state directory, making tracked planning files appear deleted.
- Add `cleanNumberedGsdVariants()` to detect and remove `.gsd <N>` entries
- Call it early in `ensureGsdSymlink()` before any existence checks
- Add `numbered_gsd_variant` doctor check that detects and auto-fixes them
- Add 19-assertion test covering directories, symlinks, mixed scenarios,
and selective removal (only `.gsd <digits>` pattern, not `.gsd-backup`)
Fixes#2205
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(forensics): opt-in duplicate detection before issue creation
Adds forensics_dedup preference (default: false) that instructs the
forensics agent to search existing issues and PRs before filing.
First-time users see an opt-in notice explaining the token cost.
Fixes#2096
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: retrigger checks
* fix(build): summary must be string[] not string in showNextAction
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A hung unit test on PR #2120 ran for 3+ hours before manual cancellation,
burning ~185 minutes of Actions quota. Add timeouts to cap runaway jobs:
detect-changes (2m), docs-check/lint (5m), build/windows (15m).
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dispose() method was not cleaning up _extensionErrorUnsubscriber,
causing the extension error handler to remain subscribed after session
disposal. This leads to memory leaks across session reloads as old
error handlers accumulate on the extension runner.
Also wrap the unsubscriber call in _applyExtensionBindings() with
try-catch so that if the previous unsubscriber throws, the new
subscription is still set up correctly.
Fixes#1936
The /api/boot endpoint relies on bridge-service.ts importing readdirSync
from node:fs to list session files. Without this import, listProjectSessions
throws ReferenceError and the route returns HTTP 500 on every request.
Add two guard tests:
- Source-level check that bridge-service.ts imports readdirSync
- Integration test that exercises the real filesystem session listing
(no listSessions mock) to catch the 500 at runtime
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two root causes fixed:
1. Route handlers gained requireProjectCwd(request) guards after the
contract tests were written. Test requests lacked a ?project= query
param, causing routes to short-circuit or throw NoProjectError.
2. resolveCredentialSource's third fallback (authStorage.hasAuth) called
the module-level getEnvApiKey import directly, bypassing the
test-injectable getEnvApiKeyFn override. Real env vars like
OPENROUTER_API_KEY leaked into tests expecting no auth.
Changes:
- Add projectRequest() helper to attach ?project= to all test route calls
- Add noEnvApiKey() stub and scoped getEnvApiKey overrides to isolate
tests from real environment variables
- Replace authStorage.hasAuth() with
authStorage.getCredentialsForProvider().length in resolveCredentialSource
to prevent env-check duplication (env is already checked via the
overridable getEnvApiKeyFn on the preceding line)
When an async bash job exceeds its timeout, killTree sends SIGTERM but
some processes (e.g. those trapping SIGTERM) never exit, causing the
promise to hang forever since the 'close' event never fires.
Add a three-stage escalation: SIGTERM -> SIGKILL after 5s grace ->
force-resolve after 3s hard deadline. Use settled guards to prevent
double-resolution when the close event races with the hard deadline.
Fixes#2186
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wrap handleCtrlZ() suspend logic in try-catch so the SIGINT listener
is removed if process.kill() or ui.stop() throws
- Dispose previous extension selector in showExtensionSelector() before
creating a new one, preventing promise leaks on rapid calls
Update pr-risk.yml and ai-triage.yml to match the versions used by all
other CI workflows:
- actions/checkout@v4 → @v6
- actions/setup-node@v4 → @v6
- node-version: '20' → '24'
Also fix unquoted $GITHUB_OUTPUT references in pr-risk.yml shell blocks
to prevent word-splitting issues.
Address three critical safety issues found during codebase audit:
- glob.rs: Explicitly drop ThreadsafeFunction after glob operation
completes to release the N-API reference immediately instead of
relying on implicit drop ordering.
- ttsr.rs: Add handle bounds validation in ttsrCheckBuffer, recover
from mutex poisoning via unwrap_or_else instead of returning errors,
cap live handles at 10,000 to prevent unbounded growth, and add
ttsrClearAll for bulk cleanup.
- image.rs: Replace unchecked (w * h * N) as usize casts with
checked_mul arithmetic that returns a descriptive error instead of
panicking on overflow.