* fix: strip model variant suffix for all auth methods, not just OAuth (#1097)
The model ID variant suffix (e.g., `[1m]` in `claude-opus-4-6[1m]`) was
only stripped for OAuth token auth. When using an API key, the suffix was
sent to the Anthropic API as-is, causing a 400 "upstream_error" because
`claude-opus-4-6[1m]` is not a valid API model ID.
The default Anthropic model is `claude-opus-4-6[1m]` (1M context variant),
so every API key user hits this on every request.
Fix: strip `[...]` suffix unconditionally for all auth methods.
* fix: update source-reading tests for post-refactor file locations
triage-dispatch.test.ts: read auto-post-unit.ts (dispatch logic moved
from auto.ts) and update comment string matches to reflect renamed
section headers.
token-profile.test.ts: read preferences-types.ts, preferences-validation.ts,
and preferences-models.ts (GSDPreferences interface and validation logic
split from preferences.ts).
* docs: add Node LTS pinning guide for macOS Homebrew users
New doc (docs/node-lts-macos.md) explains how to pin Node 24 LTS
via Homebrew to avoid running on odd-numbered development releases.
Covers brew install/link/pin, version managers as alternatives,
and verification steps.
Added notice banner in README linking to the guide.
* fix: disable reasoning for MiniMax-M2.5 in alibaba-coding-plan provider (#1003)
MiniMax-M2.5 via Dashscope's Anthropic-compatible API does not
properly support extended thinking, causing the model to get stuck
in a thinking loop. Set reasoning: false for this model entry in
the alibaba-coding-plan provider.
* docs: add Node LTS pinning guide for macOS Homebrew users
New doc (docs/node-lts-macos.md) explains how to pin Node 24 LTS
via Homebrew to avoid running on odd-numbered development releases.
Covers brew install/link/pin, version managers as alternatives,
and verification steps.
Added notice banner in README linking to the guide.
* fix: improve LSP diagnostics when no servers detected (#1082)
When lsp status returns 'No language servers configured', the output
now includes diagnostics:
- Which project markers were detected (e.g. package.json found)
- Which server commands are missing (e.g. typescript-language-server)
- Install instructions
Also added LSP troubleshooting section to docs/troubleshooting.md
with common install commands per language.
Extracts 11 hardcoded timeout, retry, compaction, and tool-default
values from 9 source files into a single constants.ts module. Each
source file now imports from the central definition, eliminating
duplicated literals and making tuning a single-file change.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add missing \u1680 (Ogham space mark) to UNICODE_SPACES in path-utils.ts
and loader.ts. Make edit-diff.ts import the shared constant from
path-utils.ts instead of maintaining an inline copy.
Rename hashlineParseText to parseHashlineText to follow the parseX()
convention used across the codebase.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Align pi-tui chalk from ^5.5.0 to ^5.6.2 (matches root, pi-ai, pi-coding-agent)
- Convert @mistralai/mistralai and openai to caret ranges (^1.14.1, ^6.26.0)
in both root and pi-ai — no intentional pin rationale found in git history,
versions were just hoisted as-is from workspace deps
- Keep gaxios@7.1.4 override pinned — intentionally set in 5c64f99 to
eliminate glob@10.5.0 deprecation warnings from transitive deps
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cap parallel async operations to prevent memory spikes when processing
large numbers of items:
- session-manager.ts: limit file loading to 10 concurrent reads
- pipeline.ts: limit job execution to 5 concurrent LLM calls
- discovery.ts: limit tool scanning to 5 concurrent scanners
Uses an inline pLimit utility in each file to avoid adding a dependency.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add argument completions for /thinking command with all 6 levels
(off, minimal, low, medium, high, xhigh) and descriptions
- Add descriptions to all GSD 2nd-level subcommand completions across
14 subcommand groups (auto, mode, parallel, setup, prefs, remote,
next, history, undo, export, cleanup, knowledge, doctor, dispatch)
- Add 35 new tests for autocomplete and fuzzy matching systems
Adds a new setting 'respectGitignoreInPicker' (default: true) that
controls whether the @ file picker respects .gitignore when listing
files. When set to false, gitignored files appear in fuzzy search
results.
Wired through:
- CombinedAutocompleteProvider: new constructor option + setter
- SettingsManager: getter/setter with persistence
- Settings selector UI: toggle in settings panel
- InteractiveMode: reads setting at init, updates provider on change
Add 7 missing subpath exports to @gsd/native (diff, gsd-parser,
highlight, json-parse, stream-process, truncate, ttsr) and create
root-level oauth.js/oauth.d.ts stub files for @gsd/pi-ai to match
the bedrock-provider pattern.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(ux): group model list by provider in /gsd prefs wizard
The model selection in configureModels() was a single flat alphabetical
list that became unwieldy with many providers. This groups models under
provider headers (e.g. "─── anthropic (24) ───") so users can quickly
scan to the right provider section.
Changes:
- ExtensionSelectorComponent: add SEPARATOR_PREFIX convention for
non-selectable group headers. Navigation skips separators, Enter
ignores them, and they render with borderAccent styling.
- configureModels: build grouped option list with provider headers
instead of flat map.
- New test: extension-selector-separator.test.ts (6 tests).
* fix(ci): use node:test instead of vitest in extension-selector test
tsconfig.extensions.json does not include vitest type declarations,
causing TS2307 in CI. Rewrite test to use node:test + node:assert
to match the convention used by other extension tests.
* fix(ci): rewrite separator test to avoid TS parameter property issue
The extension-selector component transitively imports countdown-timer.ts
which uses TypeScript parameter properties (private tui: TUI). Node's
--experimental-strip-types cannot handle these, causing ERR_UNSUPPORTED_
TYPESCRIPT_SYNTAX in CI.
Rewrite the test to verify the separator detection logic and model
grouping contract without importing the component. Duplicates the
isSeparator/nextSelectable helpers and tests them directly.
* fix: prevent data loss on crash with atomic writes, file locking, and error handling
Wave 1 of failure recovery safeguards:
1. Atomic session file rewrites (tmp+rename) — _rewriteFile() and forkFrom()
now use atomicWriteFileSync to prevent session file corruption on crash
2. Atomic auto.lock writes — crash-recovery.ts writeLock() uses tmp+rename
so the crash detection system itself can't be corrupted
3. unhandledRejection handler — catches silent process death from unhandled
promise rejections in OAuth, extensions, LSP, or MCP connections
4. try/catch in emitToolCall — matches pattern used by emitUserBash,
emitContext, and emitToolResult to prevent extension handler crashes
from killing the entire agent turn
5. File locking on session appends — prevents concurrent pi instances from
interleaving partial JSON lines in session JSONL files using the same
proper-lockfile pattern established in auth-storage.ts and settings-manager.ts
* fix: add OAuth timeouts, RPC exit detection, and command context guards
Wave 2 of failure recovery safeguards:
1. OAuth fetch timeouts — all fetch() calls across all OAuth providers
(Anthropic, OpenAI Codex, Google Antigravity, Google Gemini CLI,
GitHub Copilot) now have 30-second AbortSignal.timeout() to prevent
indefinite hangs when OAuth servers are unresponsive
2. RPC subprocess exit detection — pending requests are now rejected
when the agent subprocess exits unexpectedly, preventing indefinite
hangs in the RPC client
3. Extension command context guards — default handlers for newSession,
fork, navigateTree, switchSession, and reload now throw explicit
errors instead of silently returning success when called before
bindCommandContext()
4. OAuth error detail preservation — token refresh errors now preserve
the original error as `cause` for better diagnostics
* fix: resource cleanup, LSP retry, and crash detection on session resume
Wave 3 of failure recovery safeguards:
1. Atomic completed-units.json cleanup — milestone completion writes
now use tmp+rename pattern for consistency with auto-recovery.ts
2. Bash temp file cleanup — track temp files created for large output
and register a process exit handler to clean them up
3. Settings write queue flush on shutdown — call settingsManager.flush()
during interactive mode shutdown so queued writes aren't lost
4. LSP initialization retry — wrap getOrCreateClient with up to 2 retries
with exponential backoff (1s, 2s) for transient spawn failures
5. Crash detection on session resume — wasInterrupted() checks if last
assistant turn had tool calls without results, shows warning on resume
* fix: blob garbage collection and LSP debug logging
Wave 4 of failure recovery safeguards:
1. Blob garbage collection — BlobStore.gc(referencedHashes) removes
orphaned blobs not referenced by any session file, plus totalSize()
for monitoring blob directory growth
2. LSP JSON parse error logging — malformed LSP messages are now logged
at debug level (when DEBUG env is set) instead of being silently dropped
When completing a /gsd subcommand via autocomplete (e.g. selecting 'auto'
after typing '/gsd '), ENTER now submits immediately instead of requiring
a second press.
The selectConfirm handler already fell through to submit when the
autocomplete prefix started with '/' (completing the command name itself).
Now it also falls through when the cursor is in a slash command context
(completing an argument like 'auto', 'status', 'help').
Non-slash completions (@file references, paths) still require explicit
ENTER to submit — only slash command arguments auto-submit.
Two fixes:
1. lsp/config.ts: Use `where.exe` instead of `which` on Windows.
MSYS's `which` returns POSIX paths (/c/Users/...) that Node's
spawn() can't execute. `where.exe` returns native Windows paths.
2. lsp/client.ts: Handle spawn ENOENT error gracefully. When the LSP
server binary doesn't exist, the error event now triggers a clean
exit instead of bubbling up and crashing auto-mode.
On Windows, process.cwd() returns backslash paths (C:\Users\name\...).
When these paths are injected into system prompts, worktree context
blocks, or tool results, the model copies them into bash commands.
Bash interprets backslashes as escape characters, silently stripping
them — producing invalid paths like 'C:Usersnamedevelopmentapp-name'.
This is not a regex hack — it's a proper cross-platform boundary:
- Filesystem operations (fs, path.join, spawn cwd) use native paths
unchanged. Node handles both separators correctly for I/O.
- LLM-visible text (prompts, tool results, extension messages) uses
toPosixPath() to normalize to forward slashes. C:/Users/name/...
is valid in Git Bash, WSL bash, PowerShell, and Node.js.
Changes:
- utils/path-display.ts: New toPosixPath() utility in pi-coding-agent
package (for system prompt) and shared extension module (for
extensions that can't import from the compiled package at dev time)
- system-prompt.ts: Normalize resolvedCwd before injecting into the
'Current working directory' line
- gsd/index.ts: Normalize all process.cwd() and originalBase paths in
worktree context blocks injected into the system prompt
- bg-shell/index.ts: Normalize cwd in tool result text (start, env
actions) that the model reads and may reference in commands
- path-display.test.ts: 9 regression tests covering toPosixPath
behavior and system prompt output verification. Includes a scanner
that fails if any Windows absolute paths with backslashes appear in
buildSystemPrompt() output.
Audit scope: Checked all process.cwd() usage across pi-coding-agent
and all bundled extensions. Filesystem-only paths (join, readFile,
spawn cwd, existsSync) are correct and left unchanged. Only paths
entering LLM text are normalized.
OAuthSelectorComponent calls its onSelect callback synchronously (no
await), but the callback was async — calling showLoginDialog which
throws 'Login cancelled' on Escape. The unhandled rejection bubbled
up to the uncaughtException handler and crashed GSD.
Wrap the async work in a named function with .catch() so cancellation
errors are swallowed gracefully. showLoginDialog already handles its
own error display internally.
* feat: render native web search tool calls in TUI
The Anthropic streaming parser silently dropped server_tool_use and
web_search_tool_result content blocks, making native web search
invisible. Add ServerToolUseContent and WebSearchResultContent types,
handle both block types in the streaming parser and conversation replay,
and render them as ToolExecutionComponent in the interactive TUI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add PREFER_BRAVE_SEARCH env var to bypass native web search
Set PREFER_BRAVE_SEARCH=1 to keep Brave/custom search tools active
on Anthropic models instead of injecting native server-side web search.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: skip non-toolCall blocks in Mistral provider conversation replay
The ServerToolUseContent and WebSearchResultContent types added for
native web search don't have id/name/arguments properties, causing
TypeScript errors when the Mistral provider tried to push them as
tool calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Anthropic rate limit reset windows are typically 60-120s. The previous 60s
default, combined with the +1s buffer in extractRetryAfterMs(), meant that
virtually all rate limit retries were immediately abandoned.
300s (5 min) covers the vast majority of rate limit windows and lets the
built-in retry logic work as intended.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All major LLM provider SDKs were loaded eagerly at startup, penalizing
users regardless of which provider they actually use. This change defers
SDK loading until first API call for:
- @anthropic-ai/sdk (anthropic.ts)
- openai (openai-responses.ts, openai-completions.ts, azure-openai-responses.ts)
- @google/genai (google-vertex.ts)
The Bedrock provider already used this pattern. Now all 5 remaining
providers use the same async lazy-loader pattern:
- Static import changed to `import type` (erased at compile time)
- Module-level `let _SdkClass` cache variable
- `async function getSdkClass()` loader with singleton caching
- `createClient()` made async, uses `await getSdkClass()`
- Call sites updated with `await createClient()`
For google-vertex.ts, ThinkingLevel enum usage replaced with equivalent
string literals to eliminate the runtime import entirely.
All packages build cleanly. The startup improvement is proportional to
how many providers were installed — on typical installs this eliminates
eager loading of 30-40MB of SDK code.
Root cause: when the LLM runs `cmd &`, bash forks the process and
exits immediately. The forked process inherits Node's piped stdout/
stderr FDs. Node.js waits for all holders of those FDs to close before
firing the 'close' event — so the tool hangs until the background
process exits (which for a server is never).
Fix: add rewriteBackgroundCommand() in bash.ts. Before exec, detect
commands with a trailing & background operator and inject
>/dev/null 2>&1 before the & when stdout is not already redirected.
This severs the pipe inheritance so Node gets 'close' immediately
when the shell exits.
Guards:
- Commands already redirecting stdout (>, >>, &>, |) are not rewritten
- && (logical AND) is not affected
- & inside single-quoted strings is not affected
- A brief onUpdate advisory is surfaced when rewrite happens so the
LLM knows to prefer nohup/setsid for robust detachment
Export rewriteBackgroundCommand from pi-coding-agent for testability.
Tests: bash-background.test.ts — 12 cases covering no-op paths,
rewrite paths, compound commands, and already-safe nohup patterns.
Closes#733
gpt-5.x models (via Copilot/OpenAI/Azure) don't support 'minimal' as a
reasoning effort level — they only accept 'none', 'low', 'medium',
'high', and 'xhigh'. Setting /thinking minimal with gpt-5.4 causes a
400 error.
The openai-codex-responses provider already had this clamping, but the
openai-responses and azure-openai-responses providers passed the value
through unclamped.
Add clampReasoningForModel() to both providers that maps 'minimal' to
'low' for gpt-5.x models, matching the existing behavior in
openai-codex-responses.
Fixes the bug portion of #688
Warp terminal does not correctly re-render inverse video (\x1b[7m)
cursor indicators on arrow key movement, making the cursor appear
invisible when navigating with arrow keys.
Auto-detect Warp via TERM_PROGRAM=WarpTerminal and enable the hardware
cursor by default (same as PI_HARDWARE_CURSOR=1). The hardware cursor
is positioned correctly via CURSOR_MARKER and provides reliable visual
feedback in Warp.
Terminals that render inverse video correctly (iTerm2, Terminal.app)
are unaffected — they continue using the fake cursor by default.
During full redraws, the TUI emitted \x1b[3J (clear scrollback) before
\x1b[2J\x1b[H (clear screen + home). In terminals like Ubuntu Terminal
that honor CSI 3J, this destroyed the scrollback buffer and caused the
scrollbar/view position to jump to the top during phase transitions.
Replace with just \x1b[2J\x1b[H which clears the visible screen and
homes the cursor without touching scrollback history. This preserves
view continuity while still performing a clean redraw.
- Move @types/mime-types from dependencies to devDependencies in pi-tui
(type declarations are only needed at compile time)
- Align chalk version: upgrade root from ^5.5.0 to ^5.6.2 to match
pi-ai and avoid version skew
1. Windows: `start` command opens CMD instead of browser during GitHub
Copilot login — pass empty title arg so URL is treated as target
2. Launch banner missing Tavily provider in web search status display
3. MCPorter auto-installs via npm when not found (like ripgrep auto-download)
4. Notification prefs showing [object Object] — guard against non-boolean values
Closes#663
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix loadStoredEnvKeys divergent provider lists: add telegram_bot and
custom-openai to wizard.ts (the canonical copy used by CLI), remove
dead duplicate from onboarding.ts
- Security: add SAFE_COMMAND_PREFIXES allowlist to resolveConfigValue
to prevent arbitrary RCE via settings.json shell commands
- Security: add TOFU (Trust On First Use) model for project-local
extensions — skip untrusted .pi/extensions/ with stderr warning
- Performance: debounce sql.js MemoryStorage persistence (500ms window)
so rapid mutations coalesce into a single db.export()+writeFileSync
- Fix double lstatSync call in tool-bootstrap.ts isRegularFile
- Add 26 new tests covering all changes