mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-08-24 10:14:19 -05:00
main
1189
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
059ec5e8b0 |
fix(code-indexing): guard command detection against prototype-chain names (#1710)
detectCodeIndexingFromCommand looked up the command's first word in a plain object (CLI_COMMAND_MAPPING). A command whose first word collides with an inherited Object.prototype member resolved to the prototype value instead of undefined: the bare lookup returned the Object constructor for `constructor`, and the npx/bunx branch used `in`, which walks the prototype chain. Both falsely reported a code-indexing tool, so running e.g. `constructor ...` emitted a bogus tengu_code_indexing_tool_used telemetry event whose `tool` field was a function. Switch CLI_COMMAND_MAPPING to a Map and look up via .get(), so unknown and inherited keys both return undefined. |
||
|
|
6c7d147387 |
fix(model): preserve [1m] tag for the codex aliases (#1709)
parseUserSpecifiedModel maps the codexplan/codexspark aliases to their gpt model ids but, unlike every Claude alias (opus/sonnet/haiku/best), dropped the trailing [1m] tag. That suffix is an explicit client-side opt-in to the 1M context window (has1mContext returns 1_000_000 whenever it is present, regardless of model family), so codexplan[1m]/codexspark[1m] silently resolved to a non-1M model and the session fell back to the model default window. Append the tag the same way the other aliases do so the opt-in survives. The bare aliases are unchanged. |
||
|
|
4aec353f9c |
fix(grep): relativize content-mode paths correctly on Windows (#1704)
* fix(grep): relativize content-mode paths correctly on Windows Grep output_mode "content" split each line at the first colon to separate path from content, but a Windows absolute path starts with a drive-letter colon (C:), so it split at "C" and reassembled the original absolute path — defeating relativization (count and files_with_matches modes were already correct). Skip a leading drive-letter colon when locating the boundary. Extracts a pure relativizeContentLine helper with cross-platform tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(grep): relativize Windows context rows, not just match rows ripgrep separates the path with `:` on match rows and `-` on context rows (-A/-B/-C). The helper only looked for a boundary colon, so Windows context rows like `C:\...\file.ts-1-before` kept their absolute path. Locate the boundary as the first `:<n>:` (match; unambiguous since paths have no non-drive colon) else the first `-<n>-` (context), falling back to the first colon for line-number-less rows. This also stops date-like `-2024-` runs in filenames from being mistaken for the context boundary. Adds tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(grep): relativize content rows by stripping the known search root Review follow-up: the previous delimiter heuristic chose the first `-<n>-`, which mis-split rows when the cwd or an ancestor directory contained a date-like segment (e.g. C:\Users\proj-2024-01-15\...), and left line-number-disabled context rows (`path-content`) with absolute paths. Strip the known absolute root prefix instead: every ripgrep path under the root starts with `<root><sep>`, so removing it yields the relative path + the original delimiter + content verbatim, independent of the delimiter or whether line numbers are enabled. Paths outside the root stay absolute, matching toRelativePath. Rewrites the tests, including the date-cwd and no-line-number context-row regressions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(grep): compare the root with Windows path semantics (case/slash-insensitive) Review follow-up: stripping the root used a literal startsWith, so when getCwd() and ripgrep spelled the same Windows root with different casing or slash style (e.g. C:\USERS\PROJ vs C:\Users\proj), the prefix did not match and absolute paths leaked. Normalize the comparison for Windows roots (lowercase + treat `/` as `\`) while slicing the original line by prefix length, mirroring toRelativePath's case-insensitive path.win32 behavior. Adds casing/slash regressions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f5041e4d46 |
fix(format): roll formatFileSize over to the next unit at the 1024 boundary (#1703)
* fix(format): roll formatFileSize over to the next unit at the 1024 boundary formatFileSize selected the unit from the unrounded magnitude (kb < 1024) but displayed the rounded value, so sizes just under a boundary rendered as "1024KB"/"1024MB" instead of "1MB"/"1GB" (e.g. 1048575 bytes -> "1024KB"). Compare the rounded magnitude when choosing the unit so it promotes correctly. Adds format.test.ts covering the boundary bands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(format): assert formatFileSize(1023) renders as raw bytes The sub-KB test labelled "raw bytes" expected formatFileSize(1023) to be "1KB", but the implementation returns "1023 bytes" for values below the 1024-byte threshold, so the focused test (and the smoke-and-tests check) was red. Correct the expectation to "1023 bytes". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ba85aa6dd0 |
fix(frontmatter): expand nested brace globs in paths: correctly (#1701)
* fix(frontmatter): expand nested brace globs in paths: correctly
expandBraces used the regex `^([^{]*)\{([^}]+)\}(.*)$`, whose `[^}]+` stops
at the first `}`, so nested brace groups were corrupted: `{a,{b,c}}` became
`["a}","b","c}"]` and `src/**/*.{js,{ts,tsx}}` produced stray `}` and broken
globs — silently breaking path-scoped skill / CLAUDE.md activation. Replace
the regex with a depth-aware scan that finds the matching close brace and
splits on top-level commas only, recursing as before. Unbalanced braces fall
back to the literal input. Adds frontmatterParser.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(frontmatter): keep an empty brace group `{}` literal in paths
The balance-aware scanner expanded `{}` to a single empty alternative, so
`paths: "{}"` collapsed to [""]; parseSkillPaths and the CLAUDE.md path
parser drop that empty string and treat the file as having no path
restriction (activating everywhere). The previous regex required >=1 inner
char, so `{}` stayed literal. Restore that: treat an empty brace group as
literal while still expanding any later groups in the suffix. Adds tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
23bc49a01d |
feat(query): add lifecycle identity and terminal reasons (#1682)
* feat(query): add lifecycle identity and terminal reasons * fix(query): isolate lifecycle tracking context * fix(query): guard lifecycle metadata updates * fix(query): track lifecycle during tool waits * test(query): cover bash lifecycle metadata * fix(query): scope lifecycle tracking to request attempts * fix(query): disambiguate lifecycle abort log reason * fix(query): preserve foreground subagent lifecycle tracking * fix(query): clean up timeout and fallback lifecycle events * fix(query): emit timeout end after cleanup |
||
|
|
b9a5030b67 |
fix(status): show active provider route instead of legacy provider bucket (#1673)
* fix(status): show active provider route instead of legacy bucket The /status command collapsed many concrete providers (OpenRouter, Groq, Ollama, Fireworks AI, etc.) into a single "OpenAI-compatible" label, making multi-provider setups hard to verify and debug. When apiProvider resolves to the generic "openai" bucket, /status now uses route metadata to surface the real active route: Provider route: OpenRouter Transport: OpenAI-compatible API OpenAI base URL: https://openrouter.ai/api/v1 Model: anthropic/claude-sonnet-4.5 Credential: OPENROUTER_API_KEY configured The legacy "OpenAI-compatible" label and fallback are preserved for unknown custom base URLs. Dedicated provider buckets (nvidia-nim, minimax, codex, github, xai, gemini, bedrock, vertex, foundry, firstParty, mistral) already have accurate labels and are left untouched. Credential display uses env-var names only (never values). Transport kind and route label come from the existing descriptor-driven route metadata; no new hardcoded provider maps or network calls are introduced. * fix(status): include route status defaults * fix(status): address route status review findings * fix(status): cover route secret redaction review * fix(status): avoid duplicate route resolution * fix(status): redact base URL query credentials * fix(status): harden status URL secret redaction * fix(status): redact route secrets in status text * test(status): cover fallback URL fragment redaction * test(status): isolate route status provider imports * fix(status): redact encoded route secrets * fix(status): redact encoded query secrets safely * fix(status): redact nested encoded query secrets * fix(status): redact encoded secret substrings * fix(status): redact strict encoded secret variants |
||
|
|
02ee7c63e9 |
fix: type safety, defensive defaults, and unbounded retry prevention (#1553)
* fix: type safety, defensive defaults, and unbounded retry prevention
QueryEngine.ts:
- Import PERMISSION_MODES runtime constant and validate permissionMode
before casting in submitMessage — invalid mode strings fall back to
'default' instead of crashing with ReferenceError: PERMISSION_MODES is
not defined (fixes the runtime gap from the original PR)
- Use splice(0, length, ...messages) instead of length=0 + push() for
atomic array replacement in snip replay, so concurrent readers of
getMessages() never observe an empty state
withRetry.ts:
- Cap persistent retry loop at 100 attempts via PERSISTENT_RETRY_MAX_ATTEMPTS
constant — prevents unbounded retry (~8 hours max with exponential backoff
and 6-hour reset cap) when the unattended retry path is enabled
autoCompact.ts:
- Add MIN_AUTOCOMPACT_FAILURE_COOLDOWN_MS = 10_000 floor for
OPENCLAUDE_AUTOCOMPACT_FAILURE_COOLDOWN_MS override — prevents
misconfiguration from effectively disabling the circuit breaker
autoCompact.test.ts:
- Update test override from 5000 to 15000 to respect the new 10s minimum floor
- Add test case verifying values below the floor (5000, 9999) are rejected
and that the floor value (10000) is accepted
- Update circuit breaker retry-time expectation from 111_000 to 121_000
to account for the new 15s cooldown override
* test: enable UNATTENDED_RETRY feature in bun test scripts
The new persistent retry cap test in withRetry.test.ts needs the real
UNATTENDED_RETRY feature gate to fire, which requires passing
--feature=UNATTENDED_RETRY on the bun test command line. Enable it
in the standard test, test:full, test:coverage, and test:provider
scripts so the test sees the production gate behavior.
* test: cover persistent retry cap driven through real gate
Add a regression test that proves the persistent retry path stops
after PERSISTENT_MAX_ATTEMPTS=100 retryable 429s by driving the real
isPersistentRetryEnabled() gate (no test override seam). Also:
- Switch makeError to the new APIError() constructor so the test
errors match the real wire shape and exercise the production
canRetry/shouldRetry branches
- Add CLAUDE_CODE_UNATTENDED_RETRY to the envKeys clear list so the
gate isn't poisoned by leaked state from a prior test
- Mock src/utils/sleep.js in importFreshWithRetryModule so the
exponential-backoff delays don't slow the suite down
* test: defensively clear leaked env vars in client.test.ts
The 4 failing tests in CI (first-party Anthropic fetch wrapper, env-only
MiniMax routing, OPENAI_MODEL preservation, OpenAI shim options) all sit
at the top of the file and are sensitive to leaked env vars from prior
test files in the same process. Extend the beforeEach, afterEach, and
inline cleanup to clear OPENAI_AUTH_HEADER, OPENAI_AUTH_SCHEME,
OPENAI_AUTH_HEADER_VALUE, MIMO_API_KEY, VENICE_API_KEY, and
NVIDIA_API_KEY alongside the existing vars, and add ANTHROPIC_API_KEY /
ANTHROPIC_AUTH_TOKEN / ANTHROPIC_MODEL to the first test's inline
cleanup so it does not rely solely on the global beforeEach when run
in isolation.
* test: isolate countMcpToolTokens tests from mcp.ts env-var side effect
src/entrypoints/mcp.ts sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=true as
a top-level side effect on import. mcp.test.ts imports that file, so the
env var is leaked into the rest of the test process. In
analyzeContext.mcp.test.ts the kill switch forces getToolSearchMode() to
'standard', which makes isToolSearchEnabled() return false, so the
'keeps deferred MCP schemas excluded' test sees isDeferred=false and
returns mcpToolTokens=1000 instead of 0.
Clear the kill switch and ENABLE_TOOL_SEARCH in beforeEach (restoring
the original values in afterEach) so the test observes the production
default of tool search enabled regardless of test ordering.
* fix: validate against EXTERNAL_PERMISSION_MODES; confine persistent retry override to test-only module var
QueryEngine.ts:
- Switch SDK init permissionMode validation from the internal
PERMISSION_MODES set to EXTERNAL_PERMISSION_MODES. The internal
set can include the classifier-only 'auto' mode that is not a
valid wire value for the SDK system/init payload, so emitting
it would produce an unsupported configuration.
withRetry.ts:
- Rename PERSISTENT_RETRY_MAX_ATTEMPTS to PERSISTENT_MAX_ATTEMPTS
to match the convention of the other PERSISTENT_* constants and
re-export it as _PERSISTENT_MAX_ATTEMPTS_FOR_TEST for unit-test
assertion of the cap value (no runtime override seam).
- Hoist isPersistentRetryEnabled() into a local
persistentRetryEnabled const at the top of withRetry() so all
call sites see a consistent snapshot within a single retry
chain.
- Thread persistentRetryEnabled through shouldRetry() so its
branch is decided once per chain rather than re-reading the
feature flag and env var on every attempt.
* feat: emit telemetry when persistent retry cap is reached
* fix: surface changelog cache-write failures in migration
- Only swallow EEXIST (file already exists) errors in migrateChangelogFromConfig
- Rethrow all other write failures (permissions, disk full, etc.)
- Log migration errors instead of silently ignoring them
- This ensures migration failures are surfaced and will retry on next startup
* fix: split mkdir and writeFile in changelog migration
- Ensure mkdir runs before writeFile try/catch
- Only suppress EEXIST from writeFile, not mkdir
- Prevents EEXIST from mkdir incorrectly counting as successful write
* fix: stop overriding getGlobalConfig in user.test.ts mock
* Fix leftover conflict marker in QueryEngine.ts
* fix: remove stale retry guard and handle mkdir EEXIST
- Remove duplicate shouldRetry() call without persistentRetryEnabled arg in withRetry.ts
- Wrap mkdir in try-catch to handle EEXIST on Windows/Bun readonly folders in releaseNotes.ts
* test: don't restore OPENAI auth header env vars in afterEach
These vars were being restored from originalEnv which captures polluted values
from prior test files in the full suite. Removing the restoreEnv calls keeps
them cleared between tests, fixing 'Could not resolve authentication method'
failures in CI smoke-and-tests.
* fix: remove duplicate OPENAI_AUTH_* keys in originalEnv (TS1117)
* fix: restore OPENAI auth header snapshot and move MCP lock to top-level
- client.test.ts: restore OPENAI_AUTH_HEADER/SCHEME/HEADER_VALUE in afterEach
so the file doesn't permanently clear those globals in its worker
- analyzeContext.mcp.test.ts: move acquireSharedMutationLock/release to the
top-level beforeEach/afterEach so all env mutations in this file happen
while the shared mutation lock is held
* fix: use splice for atomic array replacement in snip replay
Restores the atomic array replacement using splice(0, length, ...messages)
instead of length=0 + push(...) that was claimed in commit
|
||
|
|
7d130e73ba |
perf: eliminate response.clone() memory doubling and cache lazy tool getters (#1478)
* perf: eliminate response.clone() memory doubling and cache lazy tool getters openaiShim.ts: - Replace response.clone() with response.text() + JSON.parse() + new Response() for non-streaming usage extraction — avoids doubling memory for large responses tools.ts: - Cache getSendMessageTool(), getTeamCreateTool/DeleteTool(), getPowerShellTool() results in local IIFEs — avoids double-invocation of lazy require() getters * test: preserve openai shim response body on parse failure * fix(openai-shim): preserve response.url routing metadata and fix regression test Addresses PR review feedback (two P2s). 1. Preserve response.url and response.type when recreating the Response after reading the body for usage extraction. new Response(bodyText) drops url to empty string, which broke create()'s /responses, /messages, and Gemini routing — descriptor routes (OpenCode /messages, Gemini /models/gemini-*) fell through to the generic OpenAI converter and returned the wrong message shape. Restore the original metadata via Object.defineProperty (shadowing the read-only prototype getter), guarded by try/catch for runtime safety. 2. Fix the flaky 'preserves response body when usage parsing fails' test. The original mock threw on the first global JSON.parse call and asserted parseCalls > 1, but Bun's native Response.json() does not go through JS-level JSON.parse, so parseCalls stayed at 1 and the assertion failed. Rewrite to scope the failure to the response body text and assert usageParseFailed + content correctness instead, which works in both Bun (native Response.json) and Node (undici). 3. Add 'preserves response.url routing metadata after body read' test that pins an Anthropic-shaped body behind a /messages URL — fails without the url fix (content becomes []), passes with it. * fix(typecheck): use 'as unknown as FetchType' to satisfy TS2352 |
||
|
|
b581bd9ece |
feat(zai): add GLM-5.2 support (#1689)
* feat(zai): add GLM-5.2 thinking support * fix(provider): derive GHE Copilot URL from base URL * fix(zai): gate GLM reasoning effort by model |
||
|
|
c4aa756689 |
feat(commands): add /update command with package-manager auto-detection (#1687)
Adds a `/update` slash command that updates OpenClaude to the latest published version, routing by how the running install is actually managed so it updates the installation the user is running. `globalPackageManager.ts` detects the owning package manager (npm, yarn, pnpm, bun) for npm-style installs and maps it to the correct global-install command. `installGlobalPackage()` and `getLatestVersion()` now consume it, so the legacy `openclaude update` CLI and the background auto-updater gain yarn/pnpm support; `getLatestVersion()` also falls back to a direct npm-registry HTTP lookup when npm isn't on the PATH. `updateStrategy.ts` factors the install-type routing and the third-party-build guard out of `src/cli/update.ts` (now shared by both entrypoints). `/update` uses it to refuse development/third-party builds, point package-manager/native/local installs at their safe update paths, and only do a global npm install when that's what's actually running — instead of always installing a stray global package. Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
cc385a6490 |
fix(ink): reduce high-write-ratio diagnostic noise (#1699)
* fix(ink): reduce high-write-ratio diagnostic noise * test(ink): cover high-write diagnostic suppression * fix(ink): preserve churn warnings at suspicious widths |
||
|
|
8cd463383d |
fix(lsp): throttle diagnostic storms (#1698)
* fix(lsp): throttle diagnostic storms * fix(lsp): account for capped storm diagnostics |
||
|
|
916f2477f3 | fix(warnings): surface perf hooks buffer guidance (#1696) | ||
|
|
df986c9275 | fix(messages): make projections tool-pair safe (#1695) | ||
|
|
2aad6fc93e |
feat(config): add OPENCLAUDE_CONFIG_DIR override (#1683)
* feat(config): add OPENCLAUDE_CONFIG_DIR env var as preferred alias for CLAUDE_CONFIG_DIR (#454) The legacy CLAUDE_CONFIG_DIR name was the only way to point openclaude at a non-default config home, which leaked Anthropic branding for a fork that has otherwise rebranded to OpenClaude. Add OPENCLAUDE_CONFIG_DIR as the preferred name. CLAUDE_CONFIG_DIR continues to work for backward compatibility; when both are set with different values, OPENCLAUDE_CONFIG_DIR wins and a one-time warning is logged. - src/utils/envUtils.ts: introduce resolveConfigDirEnv() that picks OPENCLAUDE_CONFIG_DIR over CLAUDE_CONFIG_DIR and emits a conflict warning. Memoize cache key now tracks both env vars so changing either invalidates the cached result. - src/utils/env.ts: getGlobalClaudeFile() previously read CLAUDE_CONFIG_DIR directly, missing the new alias. Route through resolveConfigDirEnv() so the global config file path follows the same precedence. - src/utils/secureStorage/macOsKeychainHelpers.ts: the "is default dir" check used by keychain service-name scoping now considers both env vars. - src/utils/swarm/spawnUtils.ts: forward OPENCLAUDE_CONFIG_DIR to teammate processes alongside the legacy var. - src/utils/openclaudePaths.test.ts: +6 unit tests covering the new alias, fallthrough, conflict warning, and resolveConfigDirEnv() in isolation. - .env.example: document both env vars and the precedence rule. Verified locally on Linux: with only OPENCLAUDE_CONFIG_DIR set, with only CLAUDE_CONFIG_DIR set (legacy still works), with both set matching (silent), with both set conflicting (warn once + OPENCLAUDE wins), with neither set (default ~/.openclaude). Memo cache invalidates across 4 sequential env transitions. Built dist/cli.mjs honors the new var and emits the conflict warning to the user. * Fix config-dir warning and docs review findings Only mark the config-dir conflict warning as emitted when a warning callback actually receives it, add coverage for warn-once and silent callers, and update web configuration docs for OPENCLAUDE_CONFIG_DIR precedence. # Conflicts: # web/src/data/configuration.ts * Align configuration docs with openclaude paths Update the configuration page settings-file table to point default users at .openclaude settings and keybindings paths, matching the new config home behavior. * Align keybindings docs with openclaude config home Update the keybindings page, keybindings docs data, and skill index to point default users at ~/.openclaude/keybindings.json. * Align skill and hook labels with openclaude paths Update bundled config/keybindings skill prompts, public skills docs, hook/trust labels, and the user memory selector to use the active OpenClaude config home paths. # Conflicts: # src/components/TrustDialog/utils.ts # src/components/hooks/SelectEventMode.tsx # src/skills/bundled/updateConfig.ts # src/utils/hooks/hooksSettings.ts * Resolve config-home paths dynamically in skill prompts Use runtime settings/keybindings path helpers for bundled skill prompts and the restricted-hooks banner so custom OPENCLAUDE_CONFIG_DIR values are reflected in user-facing guidance. * Update active command prompts for openclaude paths Point statusline, setup/onboarding prompts, plugin messages, and the external user-memory warning at the active OpenClaude settings and memory paths. # Conflicts: # src/commands/auto-fix.ts # src/commands/onboard-github/onboard-github.tsx # src/commands/plugin/ManagePlugins.tsx # src/commands/statusline.tsx * Fix remaining config path review findings * Cover dynamic config paths in UI and storage tests * Fix config path smoke failures after rebase * Fix remaining config path review findings --------- Co-authored-by: gnanam1990 <gnanasekaran.sekareee@gmail.com> |
||
|
|
e5cb589031 |
security(status): redact proxy and TLS-sensitive values in /status (#1672)
* security(status): redact proxy and TLS-sensitive values in /status Make /status safe to share in public issues and screenshots by ensuring proxy credentials, mTLS private key/cert paths, CA bundle paths, and token-bearing URLs are never printed verbatim. - Proxy URL: wrap with redactUrlForStatus (reuses redactUrlForDisplay for credential + sensitive query-param masking; additionally strips the URL fragment, which can carry tokens). - NODE_EXTRA_CA_CERTS / CLAUDE_CODE_CLIENT_CERT: wrap with redactPathForStatus, which shortens a leading $HOME to ~ so paths stay useful without leaking usernames or home directory layout. - CLAUDE_CODE_CLIENT_KEY: show the literal 'configured' rather than the path or value of a private key. Adds two small reusable helpers in src/utils/statusRedaction.ts plus unit tests, and extends status.test.ts with an integration test that asserts the full buildAPIProviderProperties output is leak-free when proxy credentials and mTLS env vars are set. * fix(status): address status redaction review feedback * fix(status): redact provider base URL secrets * fix(status): unify URL status redaction |
||
|
|
5af6f95c46 |
feat(config): add explicit provider env-file loading (#1668)
* feat(config): add explicit provider env-file loading * fix(config): handle escaped quotes in provider env files * fix(config): polish env-file parser review feedback * fix(config): preserve provider env-file precedence * test(config): cover provider env-file precedence * fix(config): preserve provider env-file values * fix(config): allow documented env-file setup vars * fix(config): preserve provider flag precedence |
||
|
|
5471e4c453 |
feat(agent-routing): assign a per-agent model from the /agents menu (#1632)
* feat(agent-routing): add user-settings route read/write helpers for the /agents UI * feat(agent-routing): add AgentRouteSelector UI for picking a per-agent model route * feat(agent-routing): open the model-route selector from the /agents detail view Co-authored-by: CommandCodeBot <noreply@commandcode.ai> * fix(agent-routing): scope route options to user settings, align cross-provider labels Build route options from getSettingsForSource('userSettings') instead of getInitialSettings(), matching the scope we persist to. Reading from the merged view could surface an agentModels key that only exists in a non-user scope; selecting it would write a shadow user-settings entry that diverges from the original (losing its base_url/api_key). Label a key as cross-provider when either base_url or api_key is present, matching readAgentRoute, so the same route is described consistently. Also assert the dangling case in the currentRouteValue test, which the old test name referenced but did not cover. * fix(agent-routing): commit custom model id on submit, match runtime cred rule The custom-model input persisted from the per-keystroke onChange, so typing the first character of a model id saved that single character and closed the selector. Track the typed value and persist only on submit (the sentinel fires the Select onChange with the full value). Cross-provider detection now mirrors the runtime resolver (toAgentRoute): a route is cross-provider only when both base_url and api_key are present. A partial entry is skipped at runtime and inherits, so readAgentRoute reports it as unconfigured (dangling) and buildRouteOptions labels it 'unconfigured, inherits' rather than claiming a cross-provider route that will not execute. * fix(agent-routing): resolve the picker against the runtime's effective route key The runtime resolver normalizes routing keys (case-insensitive, hyphen/underscore-equivalent) and falls back to default, but the picker read and wrote agentRouting with the exact agentType key. So an existing general_purpose route showed general-purpose agents as inheriting, and selecting a model wrote a general-purpose sibling that first-wins lookup ignored while the menu claimed the change took effect. readAgentRoute now matches the normalized per-agent key the resolver would use, and surfaces a default-fallback route with viaDefault. computeSetRouteUpdate/computeClearRouteUpdate overwrite or clear that existing key spelling instead of writing a sibling. The clear option is hidden for default-inherited routes since there is no own key to remove. * fix(agent-routing): do not save routes a higher-priority source overrides The runtime resolver reads merged settings (userSettings -> projectSettings -> localSettings -> flagSettings -> policySettings), but the picker read and wrote only userSettings. A project/local/policy agentRouting entry made /agents report the agent as inheriting, and selecting a model wrote a userSettings route the merged chain ignored while the menu reported success. getAgentRoute now reads the effective merged settings, so the route line reflects what runtime resolves. setAgentRoute/clearAgentRoute refuse with an explanation when a higher-priority source owns the normalized route key, and the picker surfaces that as a read-only notice instead of offering an edit that cannot take effect. * fix(agent-routing): source-aware shadow guidance + selector tests flagSettings has no file to edit (it comes from the --settings flag or SDK inline settings), so the shadow message no longer tells users to edit a nonexistent file. Extracted shadowRemediation so the error and the read-only selector notice share one source-aware string. Added focused AgentRouteSelector tests covering the shadow read-only mode (file-backed and flag sources), persisting a selected model, and surfacing a failed save without closing the dialog. * test(agent-routing): make selector set-route test deterministic The persist test selected an option by ordinal without controlling the option list and only asserted the agentType. Mock buildRouteOptions to a known single option and assert the full [agentType, modelKey] tuple, so the test proves the selected model key is what gets persisted. * test(agent-routing): stop selector mock leaking into sibling test bun's mock.module persists across files in the same process and mock.restore() does not undo it, so mocking buildRouteOptions in the selector test leaked into agentRouteSettings.test.ts and failed its buildRouteOptions assertions in the full suite (passed in isolation). Re-point the module at the real implementation in afterEach, matching the AgentsMenu.test.tsx pattern. * test(agent-routing): use real buildRouteOptions in selector test Faking buildRouteOptions via mock.module leaked into agentRouteSettings.test.ts in the full suite: bun live-updates the imported namespace, so the afterEach re-mock to that namespace re-installed the fake. The real buildRouteOptions is deterministic (built-in aliases list sonnet/opus/haiku first), so option 1 is 'sonnet' without any fake. Only the I/O wrappers stay mocked. * test(agent-routing): pin mock restore to a pre-mock exports snapshot Snapshot the real agentRouteSettings exports into a frozen const before the first mock.module call and spread/restore from that, instead of the live realRouteSettings namespace bun mutates when the module is mocked. * fix(agent-routing): resolve alias routes provider-aware, guard disabled user settings Two review findings: - A model-only route whose model is a built-in alias (sonnet/haiku/opus/inherit) was sent literally as mainLoopModel, bypassing getAgentModel()'s provider-aware fallback. On non-Claude-native providers that 404s. resolveAgentRunModelRouting now runs a bare-alias model-only route through getAgentModel (parentModel + permissionMode threaded from the callers), so it inherits the parent model the same way the agent model selector does. Real model ids pass through unchanged. - setAgentRoute/clearAgentRoute now refuse with an explanatory error when userSettings is not an enabled setting source (e.g. --setting-sources project), instead of writing a route the runtime will never load and reporting success. * fix(agent-routing): apply model-only routes to teammate spawns Pane/window teammates resolve their model through resolveOutOfProcessTeammateProvider, which returns only cross-provider overrides. A model-only agentRouting route (the common case the /agents menu writes) was dropped, so a routed teammate type with no explicit model inherited the parent instead of the saved route. Add resolveOutOfProcessTeammateModelOnly, the model-only twin of the provider resolver, mirroring runAgent's lookup order and provider-aware alias resolution, and consult it in the teammate spawn path when there is no cross-provider override. Enforce the model allowlist on the resolved model when it changes the effective model. Also add the permission-mode regression CodeRabbit requested: spy on getAgentModel to assert the mode is forwarded into alias resolution. * fix(agent-routing): guard agentModels key shadowing and honest clear label The picker builds options from userSettings, but agentModels merges by source priority, so a user-level route to a key that a higher source (project/local/flag/policy) also defines resolves to that higher entry, not the current-provider model the option promised. getRouteShadowSource only checked agentRouting keys, so this collision was not surfaced. Add findModelKeyShadowingSource / getModelKeyShadowSource and refuse to save a model-only route whose key is shadowed by a higher source (unless the user already owns that key in userSettings). buildRouteOptions now flags shadowed keys so the conflict is visible before selecting. Also fixes the clear-route label: clearing only removes the agent's own routing key, so when a default route is configured the agent falls back to the default, not the parent model. The label and PR description now say so instead of promising parent inheritance. * fix(agent-routing): reject shadowed model keys even when userSettings owns them The previous shadow guard skipped the check when userSettings already defined agentModels[modelKey], but that does not make the route take effect: agentModels merges by source priority, so a higher-priority project/policy entry for the same key still wins and the agent resolves to that provider/model. The picker even flagged the key as shadowed while the save path let it through, so offer and save disagreed. Drop the ownsKey carve-out so setAgentRoute rejects whenever a higher source defines the key. Extract collectShadowedModelKeys as the single definition of 'defined above userSettings' that both the offer flag (getShadowedModelKeys) and the save guard (findModelKeyShadowingSource) derive from, and add a test locking the two paths in agreement. * test(agent-routing): cover setAgentRoute shadow guard and model-only teammate spawn Adds direct setAgentRoute() coverage for the higher-priority-shadow rejection and the allowed user-only-key save, and an AgentTool teammate-spawn regression that a model-only route reaches spawnTeammate as the resolved model with no cross-provider override. * test(agent-routing): cover custom model id submission persists full id * test(agent-routing): match focus glyph cross-platform in custom-id test The custom model regression test waited for the ❯ focus pointer, but ink renders figures.pointer as ">" on Windows, so the wait timed out in a Windows checkout before typing the id. Match figures.pointer directly so the wait tracks whatever glyph the renderer emits on each platform. * fix(agent-routing): let the route picker own Esc while open AgentDetail kept its parent confirm:no (Esc -> onBack) handler active after switching into routing mode. Since the Confirmation context was registered first, a bare Esc resolved to confirm:no and exited the detail view instead of resolving to the nested select's select:cancel, despite the picker copy telling users Esc goes back. Gate confirm:no with isActive=!routing so the route selector owns Esc and a single Esc only closes the picker. Add an AgentDetail regression covering m then Esc. * fix(agent-routing): scope Esc to the route picker and keep in-process teammate routes Two follow-ups from review of the per-agent model routing UI. The /agents detail view wraps AgentDetail in a Dialog whose own confirm:no (onCancel) stayed active while the route picker was open. As the first-registered Confirmation context it swallowed Esc and dropped back to the agent list instead of just closing the picker. AgentDetail now reports its picker state up through onRoutingChange, and a small AgentDetailDialog wrapper feeds that into the Dialog's isCancelActive so the picker's Select owns Esc while it is open. In-process teammates run the same runAgent() as normal subagents, but the synthetic agent definition overwrites agentType with the teammate's display name, so the original subagent_type that agentRouting is keyed on was lost and the configured cross-provider route never resolved (the teammate ran the routed model on the parent provider). The original subagent_type is now carried through the in-process handoff and used as the routing key, matching what pane and window teammates already get by re-resolving from their CLI identity. Adds a regression test for the picker Esc interaction through the Dialog wrapper and resolver tests proving the route resolves from the original subagent_type rather than the teammate name. --------- Co-authored-by: CommandCodeBot <noreply@commandcode.ai> |
||
|
|
4cf981200f |
feat(cache): classify prompt-cache breaks by reliability (#1693)
* feat(cache): classify prompt-cache breaks by reliability * fix(cache): stabilize prompt-cache break metadata detection * fix(cache): honor legacy OpenAI base fallback * fix(cache): normalize OpenAI base URL hints * fix(cache): align cache-break provider flag truthiness * fix(cache): ignore undefined OpenAI base hints * fix(cache): sanitize prompt cache route labels |
||
|
|
beab67b44a |
fix(session-title): harden generated title handling (#1691)
* fix: harden session title generation * fix(session-title): preserve prompt fallbacks after generation failure * fix(session-title): address fallback review findings |
||
|
|
6fbbf2dffc |
fix(provider): honor explicit CLAUDE_CODE_USE_OPENAI=0 on fresh startup (#1690)
When no provider profile is saved, buildStartupEnvFromProfile defaults to the Gitlawb Opengateway profile, which sets OPENAI_BASE_URL and re-enables the OpenAI-compatible route. If the user has explicitly opted out with CLAUDE_CODE_USE_OPENAI=0, this fallback overrode that choice and the startup validator then reported a spurious "OPENAI_API_KEY is required" warning, even in a clean env -i environment. Honor the explicit opt-out in the no-saved-profile branch by returning the process env unchanged instead of injecting the default profile. Saved non-OpenAI profiles are unaffected and still applied. Closes #1245 |
||
|
|
23cfc242ed |
fix(query): add activity-aware query guard leases (#1686)
* fix(query): add activity-aware query guard leases * test(query): cover tool query activity lifecycle * fix(shell): align runtime and lease timeouts * fix(query): cap shell timeouts to query budget |
||
|
|
bd39c3a558 |
Add GitHub Enterprise Copilot support (#1685)
* feat(github): add GitHub Enterprise Copilot support Add GitHub Enterprise onboarding paths for Enterprise URL and direct Copilot key auth, including URL normalization and credential storage metadata for Copilot keys. Route GitHub Enterprise requests through the Enterprise Copilot API, update device-flow endpoints, preserve profile/startup env for GHE, and ensure direct Copilot keys win over stale OpenAI auth. Add a GitHub Enterprise gateway descriptor and generated integration artifact entry, plus regression coverage for request routing, validation, shim auth, profile handling, onboarding env cleanup, and secure-storage hydration. Validation run: bun run build; bun run typecheck; bun run typecheck:type-tests; bun run integrations:check; bun run test:provider; bun run test:provider-recommendation; bun run security:pr-scan; python -m pytest -q python\\tests. Known unrelated full-suite failures remain in export direct filename, marketplace cache finalization, and platformStorage tests. * fix(github): address enterprise review feedback Preserve explicit OPENAI_API_KEY for GitHub shim requests unless a direct GITHUB_COPILOT_KEY is set. Keep saved GitHub Enterprise URLs when reactivating an existing onboarding login and ignore literal undefined Enterprise env values. Persist and validate github-enterprise provider profiles without downcasting them to public GitHub. * fix(github): honor enterprise env startup intent Treat GITHUB_ENTERPRISE_URL and GITHUB_COPILOT_KEY as complete GitHub startup selection so saved profiles do not override env-only Enterprise setups. Normalize path-bearing Enterprise URLs to the instance origin before constructing OAuth and Copilot token endpoints. * test(github): cover enterprise device flow routing Exercise requestDeviceCode with a path-bearing gheUrl and assert the actual fetch URL uses the normalized Enterprise device-code endpoint. |
||
|
|
3135e731c9 |
fix: WSL stdin handling (#1679)
* Fix WSL stdin handling Keep the default App stdin path in readable mode without resuming the stream, so WSL PTYs do not switch into flowing data mode and freeze after typed prompts or slash commands. Preserve the opt-in data-mode behavior by only calling resume() for OPENCLAUDE_USE_DATA_STDIN. Add App stdin mode coverage for the default readable path and data-mode fallback. Harden timeout/status notice behavior and tighten test isolation for the full local check suite. Validation: bun run build; bun run typecheck; bun run typecheck:type-tests; bun run security:pr-scan; bun run check; WSL2 PTY smoke with node dist/cli.mjs --bare --setting-sources user and /help input. # Conflicts: # src/hooks/fileSuggestions.test.ts * Address PR review feedback Restore the file-index test mock alongside the other file suggestion dependency mocks to prevent cross-suite leakage. Cover OPENCLAUDE_USE_READABLE_STDIN=0 as the backward-compatible data-mode env gate. Log status notice predicate failures to the debug log before treating the notice as inactive. Validation: bun test src\\ink\\components\\App.test.tsx src\\hooks\\fileSuggestions.test.ts --max-concurrency=1; bun run typecheck; bun run check. # Conflicts: # src/hooks/fileSuggestions.test.ts |
||
|
|
2351811b3c |
docs: add AI agent contribution guidance (#1676)
Add AGENTS.md with repository-specific guidance for AI coding agents, including runtime conventions, validation commands, provider-change expectations, and limits on new Python work. Update CONTRIBUTING.md with maintainer-directed contribution expectations, CodeRabbit follow-up requirements, duplicate PR guidance, and a link to AGENTS.md. |
||
|
|
da551e6d05 |
fix(model): preserve [1m] tag for the 'best' alias (#1671)
parseUserSpecifiedModel appended the [1m] (1M-context) tag for the opus, sonnet, and haiku aliases but not for 'best'. Since 'best' resolves to the same model as 'opus' (getDefaultOpusModel), 'best[1m]' silently dropped the 1M-context request while 'opus[1m]' kept it — so a user pinning 'best[1m]' lost the larger context window. Append the tag for 'best' as well, matching the other aliases. Add a relational regression test (best[1m] tracks opus[1m], tag is case-insensitive and not duplicated). |
||
|
|
de6b6bdd03 |
fix(context): treat Opus 4.7 as 1M-context capable in modelSupports1M (#1670)
modelSupports1M only matched claude-sonnet-4 and opus-4-6, but the firstParty default Opus is now claude-opus-4-7 and the default session model is claude-opus-4-7[1m] (getDefaultMainLoopModelSetting). With 4.7 unmatched, resolveSkillModelOverride drops the [1m] suffix when a skill specifies `model: opus` on an Opus 4.7 session, silently downgrading the effective window from 1M to 200K and tripping autocompact / "Context limit reached" at ~23% apparent usage. The same predicate gates the beta-based 1M window path in getContextWindowForModel. Add opus-4-7 to the predicate (the @[MODEL LAUNCH] checklist item missed at the 4.6 to 4.7 default bump) and cover modelSupports1M with regression tests, including the disable-switch path. |
||
|
|
1b33fa62b8 |
fix(provider): match xAI base URL by hostname, not 'x.ai' substring (#1669)
The xAI credential-mirroring checks in providerProfiles.ts used
`baseUrl.toLowerCase().includes('x.ai')`. That substring matches unrelated
hosts — e.g. `vertex.ai`, `essex.ai`, `max.ai` all contain `x.ai` — so an
OpenAI-compatible profile pointed at such a host was wrongly treated as xAI:
its api key got mirrored into XAI_API_KEY and route detection flipped to
`xai`, breaking model routing.
Route the three sites (profileSecretsAreComplete, the openAI profile-env
builder, and the active-profile env builder) through the existing
`isXaiBaseUrl` helper, which matches `hostname === 'api.x.ai'` — consistent
with how isFireworksBaseUrl/isNearaiBaseUrl are already used in this file.
Add a regression test asserting a `vertex.ai` profile does not set
XAI_API_KEY and is not detected as the xai provider.
|
||
|
|
544b857876 |
fix(settings): correct stale settings path references (#1666)
* fix(settings): correct OpenClaude settings paths * fix(settings): address review path clarity * fix(sandbox): protect OpenClaude settings in changed cwd |
||
|
|
29aea4969d |
fix(provider): centralize provider secret redaction (#1665)
* fix(provider): centralize provider secret redaction * fix(system-check): prefer base URL route credentials * fix(provider): avoid false credential matches * fix(provider): redact jwt-shaped tokens * fix(provider): redact embedded diagnostic secrets * test(system-check): isolate provider env keys |
||
|
|
e733908a91 |
fix(claude-desktop): add native Windows support for MCP server import (#1653)
* fix(claude-desktop): add native Windows support for MCP server import
SUPPORTED_PLATFORMS in platform.ts only included 'macos' and 'wsl', causing getClaudeDesktopConfigPath() to throw on native Windows. Additionally, no Windows path handler existed -- the function only handled macOS then fell through to WSL-specific /mnt/c/Users/... paths.
Changes:
- Add 'windows' to SUPPORTED_PLATFORMS in platform.ts
- Add Windows path handler in getClaudeDesktopConfigPath() using %APPDATA%
- Update error messages to reflect Windows support
Impact: unblocks Claude Desktop MCP server import on native Windows.
* docs(claude-desktop): add docstrings to satisfy coverage threshold
* fix(claude-desktop): re-throw APPDATA error and add test coverage for Windows path
The APPDATA error was being swallowed by readClaudeDesktopMcpServers() catch-all, silently returning {} instead of surfacing the misconfigured environment to the user.
Changes:
- Re-throw APPDATA error in readClaudeDesktopMcpServers() so users see the configuration issue instead of silently getting no servers
- Add claudeDesktop.test.ts with tests for the Windows APPDATA path and missing-APPDATA error
Impact: users on Windows will now see a clear error if APPDATA is unset, instead of silently getting an empty server list.
* test(claude-desktop): use try/finally for env isolation and explicit fixture values
* fix(claude-desktop): opt-in import, update help text, mock platform in tests
* test(claude-desktop): save and restore real platform module to avoid leaking mock to other tests
mock.module is process-global in bun. Previous approach registered a mock for ./platform.js that affected all other test files importing from it, breaking the full test suite on CI. Now saves the real module via dynamic import before registering the mock, and restores it in afterAll.
* fix(claude-desktop): use win32.join for Windows APPDATA path to fix cross-platform test failure
path.join() on Linux uses forward slashes, producing mixed separators when joining a backslash-based APPDATA value. Since %APPDATA% is always a Windows path, use win32.join() which always uses backslashes regardless of host OS. Also update the test expectation to use win32.join() so it matches on any platform.
* test(claude-desktop): add readClaudeDesktopMcpServers rethrow test and fix env restore
- Added test for readClaudeDesktopMcpServers verifying APPDATA error surfaces instead of being swallowed
- Added restoreAppData helper that uses delete when original was undefined to avoid string coercion to 'undefined'
- Updated existing tests to use restoreAppData
* test(claude-desktop): remove mock.module to fix CI, add pure-logic path test
mock.module is process-global in bun and cannot be safely restored, causing getPlatform() to return 'windows' for all subsequent tests on CI — which triggered findGitBashPath() -> process.exit(1) on Linux.
- Remove mock.module('./platform.js', ...) entirely
- Guard Windows-dependent tests with if (process.platform !== 'win32') return
- Add pure-logic test using win32.join that validates path construction without any module mocking, works on all platforms
* refactor(claude-desktop): extract pure helper getWindowsClaudeDesktopConfigPath
Extract APPDATA path construction and missing-APPDATA error into a pure, synchronous helper function that takes appData as a parameter. This lets the core Windows logic be tested on any platform without mocking process.env, addressing CodeRabbit's concern that the previous if(isWindows) guard left changed behavior untested on CI.
- Two new unconditional tests exercise the helper on all OS runners
- if(isWindows) integration tests remain for end-to-end coverage on Windows
- No behavioral change
* revert opt-in import, update readClaudeDesktopMcpServers jsdoc
Addresses reviewer findings: - Revert defaultValue back to {t14} in MCPServerDesktopImportDialog — the opt-in UX change is scope creep for this Windows-support PR - Update readClaudeDesktopMcpServers JSDoc to document that it can throw on Windows when APPDATA is unset
|
||
|
|
a1b3346f65 |
feat(cli): add local background sessions (#1642)
* feat(cli): add local background sessions Add local detached background sessions backed by an OpenClaude-owned registry under the resolved config directory. - implement --bg spawning plus ps, logs, logs -f, kill, and an explicit attach limitation - harden registry metadata validation, atomic writes, ID/name collision handling, and terminal-name reuse - precreate child log files with precise ownership cleanup and register metadata only after spawn succeeds - verify live PIDs against the session command before treating registry entries as running - wait for process-tree termination and escalate to SIGKILL before marking sessions killed - skip live local background sessions during --continue transcript selection - preserve Node heap flags for detached children while avoiding stale launcher relaunch state - handle -- separators so dash-prefixed prompts remain positional - document storage, safety model, name reuse, and the current attach limitation Validation: - bun test - bun run typecheck - bun run smoke - isolated built-CLI --bg/ps/logs/kill smoke - CodeRabbit review findings addressed * test(utils): prevent bg registry mock leakage Restore complete bg registry and UDS module mocks after conversation recovery tests so Bun's process-global mock.module registry cannot leak partial module exports into later CLI tests. CI exposed this under Bun 1.3.13 when conversationRecovery.test ran before the bgRegistry and bg CLI test files. * test(utils): exercise bg registry without global mock Replace the conversation recovery bgRegistry module mock with real registry metadata backed by a short-lived live child process. This keeps UDS as the only mocked boundary and avoids leaking a mocked registry module into later CLI registry tests under Bun 1.3.13. * test(utils): isolate background registry state Stop the conversation recovery test from using process-wide bgRegistry mocks or real child processes by injecting the live-session dependencies directly. Pin and serialize the bg registry test config directory through the shared env mutation lock so path/cache state cannot leak from neighboring tests under Bun CI ordering. * test(utils): document Bun mock restoration Explain why conversation recovery tests re-register full module exports after mock.restore(), matching the CodeRabbit-requested Bun 1.3.13 isolation workaround. * test(cli): isolate background registry root Avoid relying on process-wide CLAUDE_CONFIG_DIR state in bgRegistry tests. Use a registry-local test root override so CI file ordering and mocked path modules cannot redirect background session metadata into another test's temp directory. * test(utils): cover live session fallback paths Add focused coverage for collectLiveBackgroundSessionIds when UDS discovery fails but registry data remains available, and when registry refresh fails but UDS data remains available. * fix(cli): harden background session management Validate persisted and newly-created background session PIDs before exposing them to management commands. Reserve named live sessions with an atomic registry write, release reservations when sessions become terminal, and cover concurrent duplicate-name attempts. Split local session management dispatch from background spawning so ps/logs/attach/kill avoid provider startup while --bg still inherits profile routing. * fix(cli): address background session review findings Preserve positional prompts when --bg is combined with optional-value flags such as --debug. Recover stale name reservations whose owner metadata is missing or terminal while preserving in-flight reservations from live creators. Cover both reviewer findings with focused parser and registry regression tests. * fix(cli): respect delimiter for background flags Limit background and print-mode flag detection to arguments before the -- delimiter so flag-shaped prompts remain positional. Keep optional resume/from-pr flags out of the required-value table and add regressions for delimiter and optional-flag prompt handling. * refactor(cli): share delimiter argument helper Move args-before-delimiter handling into the existing dependency-free CLI args utility. Use a dynamic import from the entrypoint so background flag routing shares the helper without adding top-level module load to version and management fast paths. * test(cli): cover background entrypoint routing Export the CLI entrypoint for controlled tests and add isolated importer injection so runtime routing tests do not leak global module mocks. Replace the delimiter source-layout assertion with execution-level coverage for management commands, real background flags, and flag-shaped prompt text after --. * fix(cli): preserve background resume selectors Keep space-separated --resume, -r, and --from-pr values attached when building background child args. Mark live background sessions stale when PID command identity cannot be read, avoiding termination of reused unrelated PIDs. * fix(cli): track unknown background session identity Represent unreadable live PID identity as a non-terminal unknown state so active sessions stay excluded from resume selection. Refuse to terminate unknown live PIDs because the process command cannot be positively matched to the background session. * fix(cli): honor background resume selectors Avoid adding a generated --session-id to non-forked background resume launches so the spawned print-mode child satisfies the existing resume/session-id contract. Pass --from-pr through headless print mode and resolve PR-linked sessions through the shared conversation recovery path. Add regression coverage for background resume launch args and PR selector matching. * fix(cli): treat PR resume as headless resume source Include --from-pr in print-mode resume guards so PR-linked headless resumes can run without a prompt and share resume-only options. Skip eager startup hooks for headless PR resumes and add explicit --session-id launch coverage. * fix(cli): keep background PR resumes live Resolve non-forked --from-pr background launches to the selected transcript id before writing registry metadata. Preserve PID identity refresh for PR-resume children by matching the stored invocation when argv does not carry the transcript id. Add regressions for launch registration and registry refresh. * test(cli): cover PR resume lookup failures Add regression coverage for non-forked background --from-pr launches when the selector cannot be resolved. Verify the launch planner returns the same clear error used by handleBgFlag(). |
||
|
|
1aabe261db |
feat(bughunter): make /bughunter public + add /bughunter-security & /bughunter-perf with robust fallback prompts (#1621)
* feat(bughunter): split into /bughunter, /bughunter-security, /bughunter-perf
Replace the single /bughunter command with three siblings that share a
common prefix:
/bughunter — general bug hunt (existing prompt, untouched)
/bughunter-security — OWASP-aligned, exploit-driven, confidence ≥ 8
/bughunter-perf — hot-path complexity, sync I/O, leaks, N+1
Both new subcommands are prompt commands built with
createMovedToPluginCommand so they migrate to the bughunter marketplace
plugin unchanged once it ships. While the marketplace is private they
inline the full audit prompt (frontmatter + !`git ...` blocks) just like
the existing /bughunter.
All three stay in the public COMMANDS list (not INTERNAL_ONLY_COMMANDS)
so non-ant users can invoke them. clearCommandMemoizationCaches() now
also flushes the zero-arg COMMANDS() and builtInCommandNames() memos so
tests can switch USER_TYPE mid-run without poisoning the cache.
Adds regression tests in src/commands.test.ts covering:
- bughunter stays public for non-ant users
- bughunter-security and bughunter-perf are in the public list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(bughunter): remove orphan index.js after .js → .ts rename
The bughunter command directory was renamed from a single .js file to
index.ts in the previous commit, but git tracked them as separate paths
so the old .js was left in the tree. Drop it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(bughunter): enhance fallback prompts for robustness in non-git environments
- Add graceful error handling to all git commands in fallback prompts (|| echo fallbacks)
- Add explicit non-git fallback guidance in Phase 1 for all three commands
- /bughunter: search for entry points, core business logic, recently modified files
- /bughunter-security: search for auth/middleware, validation, DB, config, upload code
- /bughunter-perf: search for handlers, loops, data access, serialization, build configs
- Improve context labels to clarify git context may be empty
* fix(bughunter): address CodeRabbit feedback
- Fix test isolation: restore USER_TYPE/IS_DEMO env vars in finally blocks
- Add non-git fallback test cases for all three bughunter commands
- Fix bash pipeline issue: replace if/then/else subshells with simple git commands + static fallback text in template
- Fix output format contradiction: remove LOW confidence from scoring (Phase 3 drops LOW, so scoring only includes Critical/Medium)
* fix(test): correct case and prefix in git fallback assertions for bughunter-security and bughunter-perf tests
* fix(test): add missing opening parenthesis in bughunter test assertions
* fix(bughunter): complete non-git fallback and propagate allowedTools
- Fix git commands in all three prompts to always succeed with fallback text (using || echo)
- Modify createMovedToPluginCommand to accept allowedTools parameter
- Add allowedTools to all three bughunter commands so slash-command turn grants declared tools
- Parse allowed-tools from frontmatter at command creation time
* fix(bughunter): complete non-git fallback and allowedTools propagation
- Fix git commands in prompts to always succeed with fallback text (using || echo)
- Modify createMovedToPluginCommand to accept allowedTools parameter
- Add allowedTools to all three bughunter commands so slash-command turn grants declared tools
- Fix RECENTLY COMMITTED FILES command to avoid command substitution (permission check rejects )
- Update tests to accept shell tool's '(Bash completed with no output)' for empty results
- Use runWithCwdOverride and additionalWorkingDirectories for proper test isolation
* fix(bughunter): prevent shell injection via user-provided args
The user-provided scope was interpolated into the prompt template BEFORE
executeShellCommandsInPrompt() ran, so any !command or ```! block
syntax in the args would be interpreted and executed as shell commands.
Fix: parse frontmatter from the raw template and run shell execution first
(with {{ARGS}} still in place — inert to shell patterns), then replace
{{ARGS}} with the user scope on the processed output. This ensures args
are never fed through the shell command parser.
* refactor(bughunter): use createGetAppStateWithAllowedTools helper
Replaces duplicate inline getAppState overrides across all three bughunter
commands (bughunter, bughunter-security, bughunter-perf) with the shared
helper from src/utils/forkedAgent.ts. This:
- Eliminates ~30 lines of duplicated permission context modification
- Merges allowedTools with existing alwaysAllowRules.command (vs overwrite)
* fix(bughunter): address jatmn review - String.replace special patterns + test isolation
- Replace '{{ARGS}}' with a replacer function () => scope instead of
the plain string 'scope'. JavaScript's String.replace treats $&, $',
$', , 32855 specially even in string replacements, so a scope like
'src/auth $&' would render as 'src/auth {{ARGS}}' instead of literal
text. The replacer function bypasses all special patterns.
- Restore USER_TYPE and IS_DEMO env vars in the injection regression
test's finally block, matching the isolation pattern used by all other
bughunter tests.
* fix(bughunter): make fallback prompt generation work on Windows
Wrap executeShellCommandsInPrompt() in a try/catch in all three bughunter
commands. On platforms where bash is unavailable (e.g. Windows without Git
Bash), the bash-specific shell syntax (2>/dev/null, | head -N) would cause
executeShellCommandsInPrompt to throw MalformedCommandError, preventing the
prompt from being generated at all.
The catch handler replaces the !`command` inline patterns with a static
placeholder, allowing the LLM to still receive the full audit instructions
and non-git search strategies in Phase 1.
* fix(bughunter-perf): remove Low severity contradiction
The summary line included Low: L but Phase 3 drops non-measurable findings
and exclusions remove micro-optimizations. Low findings (measurable but
not user-visible) would never survive the filter, so remove Low from the
severity categories and summary line.
fix(bughunter-security): align log-forging exclusion with A9 criteria
Exclusion #11 blocked all log spoofing/forging, but A9 says to flag
log injection when it enables audit-trail forgery. Narrowed the exclusion
to allow concrete audit-trail attacks through while still excluding
generic non-exploitable logging suggestions.
* fix(bughunter-security): tighten log-forging exclusion threshold
Reword exclusion #11 to require concrete evidence of a log-entry or
structured-field forgery path, not merely unsanitized user input.
* fix(bughunter): preserve fallback text on Windows/no-bash path
Replace generic '(Shell execution unavailable)' placeholder with a regex
that extracts the || echo "..." fallback text from each shell command.
This ensures the prompt shows meaningful messages like
'(If empty: not a git repository or git unavailable)' even when bash is
unavailable (e.g. Windows without Git Bash), matching what Linux users see
from working shell execution.
Also make injection test assertion platform-agnostic — accept either bash
output or the static echo fallback text.
* refactor(test): extract duplicate mockContext into createMockToolContext helper
The three non-git fallback tests each had an identical ~42-line mockContext
object. Moved it to a shared createMockToolContext(cwd, commands) helper
and a FULL_GIT_COMMANDS constant. Also updated the injection test to use
the same helper. Net -89 lines.
* fix(createMovedToPluginCommand): only grant allowedTools when fallback prompt runs
The ant (USER_TYPE === 'ant') branch returns a plugin-install notice that
doesn't need Read/Glob/Grep/Bash tools, but allowedTools was statically
attached to the command object. This caused processSlashCommand to grant
turn-scoped permissions for tools that were never used.
Changed to a getter that returns undefined in the ant branch, so the
plugin-install notice runs without unnecessary tool permissions.
* fix(bughunter): simplify shell commands to single git commands, narrow catch to surface interruptions
* fix(bughunter): surface permission-denied/aborted shell preprocessing, fix Windows cleanup
* fix(dragDropPaths.test): resolve package.json relative to test file, not process.cwd()
* fix(commands.test): restore original cwd in rmRetry, guarantee env/cache cleanup on rm failure
* fix(bughunter): bound diff to 400 lines, swap HEAD~10 for git log -10
Address both P2 reviewer findings on feat/bughunter-command-v3-new.
(1) Fresh-repo HEAD~10 lookup stripped every snippet. In a one-commit
repo, `git diff --name-only HEAD~10..HEAD --diff-filter=AM` exits
128 (HEAD~10 doesn't resolve). The shell-execution catch then ran
the outer "strip all snippets" fallback, leaving git status /
diff --cached / diff HEAD empty even though those commands would
have produced useful context. Switched to `git log -10 --name-only
--diff-filter=AM`, which works at any history depth and yields the
same file list. Applied to bughunter, bughunter-security, and
bughunter-perf.
(2) Diff cap removed in
|
||
|
|
d5588ea80d |
feat(context-collapse): opt-in between-turns context collapse (span summarization) (#1619)
* feat(context-collapse): implement context collapse for proactive context management
* feat(context-collapse): add turn-boundary helpers for span selection
* feat(context-collapse): deterministic turn-anchored span selection
* feat(context-collapse): code-computed span risk score
* feat(context-collapse): ctx-agent summarization instruction
* feat(context-collapse): implement ctx-agent span summarization spawn
* fix(context-collapse): make runtime activation opt-in (CLAUDE_CONTEXT_COLLAPSE)
* fix(context-collapse): address review feedback on restore state and test rigor
- restoreContextCollapseState now resets armed/lastSpawnTokens up front so a
snapshot-less restore cannot carry stale spawn state across sessions.
- projectView reuses a stable timestamp from the replaced span instead of
new Date(), keeping the read-side projection deterministic.
- Strengthen the disabled-state and turn-boundary assertions, drop an internal
renderToolUseMessage assertion, and isolate the operations/persist/spawn tests
from shared module and CLAUDE_CONTEXT_COLLAPSE env state.
* test(context-collapse): re-init enablement in persist.test hooks
resetContextCollapse() does not re-read CLAUDE_CONTEXT_COLLAPSE, so the
afterEach env delete left enabled=true in module state, leaking to the
next test file. Call initContextCollapse() in both hooks so module
enablement stays synced to the env var.
* test(context-collapse): stop spawnCtxAgent module stubs leaking across files
spawnCtxAgent.test.ts stubs shared modules (tokens, forkedAgent, messages,
analytics, log, spanSelection) via mock.module in beforeEach. bun's
mock.restore() does not undo mock.module, so the tokens stub (() => 100000)
bled into autoCompact/microCompact/runAgent tests run later in the full serial
suite, making them see every conversation as over-threshold (4 spurious
failures in test:full, all green in isolation).
Restore each stub to its real implementation in afterEach. The reals are
snapshotted into plain objects up front because 'import * as' yields a live
namespace that mock.module mutates in place, so holding the namespace would
restore the stub. autoCompact.js is deliberately not restored here since
autoCompact.test.ts re-imports it fresh via a cache-busting nonce.
Also reset+reinit the collapse module in afterEach so enabled state stays
synced to the now-unset env var.
* test(context-collapse): also restore autoCompact stub from spawnCtxAgent
The getEffectiveContextWindowSize stub on ../compact/autoCompact.js was the one
module the previous commit left unrestored, on the assumption that restoring it
would clash with autoCompact.test.ts's nonce re-import. It doesn't: the nonce
import uses a different specifier, and the snapshot restore is keyed by the
plain specifier. compressToolHistory imports getEffectiveContextWindowSize and
sizes tool-history truncation from it, so the leaked 20000-token window made it
fully omit tool results ('chars omitted') instead of mid-truncating
('[…truncated') for large-context models, failing the openaiShim compression
tests in the full serial suite. Restore all seven mocked modules.
* fix(context-collapse): re-arm after reset and gate ctx_inspect on opt-in
resetContextCollapse() left armed=false while enabled stayed true, so the
first /compact, main-thread compaction cleanup, or rewind permanently
disabled collapse for the rest of an opted-in session. Reset now mirrors
restoreContextCollapseState and sets armed=enabled.
CtxInspectTool.isEnabled() returned true unconditionally, advertising
ctx_inspect to the model in every default session even when the runtime
opt-in was off. It now returns isContextCollapseEnabled(). The opt-in is
also exposed as the contextCollapseEnabled global config key, so it is
reachable through /config instead of only the CLAUDE_CONTEXT_COLLAPSE env
var.
* refactor(context-collapse): drop no-op ternary in drainStaged persist call
The (stagedQueue.length > 0 ? 0 : 0) subtrahend always evaluated to 0, so
this is just persistCommits(processed.length).
* fix(context-collapse): persist commits before advancing the snapshot
drainStaged removed processed spans from the staged queue and then fired
persistCommits and persistSnapshot in parallel. If the snapshot write (which
no longer lists those spans as staged) landed while the commit write failed
or the process died between them, restore would find the spans neither staged
nor committed and the collapse would disappear on resume. Chain the snapshot
write after the commit write so the commit log is durable first.
* fix(context-collapse): project committed collapses on the query path, fix opt-in reach
Three issues from review:
- Committed collapses were never re-applied to the model input. The query path
calls applyCollapsesIfNeeded but only drained staged spans; projectView (which
replays the commit log) ran only in /context. Since messagesForQuery is rebuilt
from full REPL history each turn and the commit log is repopulated on resume,
the archived spans returned to the model on the next turn, undoing the collapse.
applyCollapsesIfNeeded now runs projectView first (idempotent). Adds a
regression that a committed collapse changes the next query input.
- Cache-safe params were saved only for exact repl_main_thread/sdk sources, but
the REPL tags non-default output styles as repl_main_thread:outputStyle:*, so
those sessions left the ctx-agent without params (empty spawns). Matches
repl_main_thread:* now, via a small tested helper.
- contextCollapseEnabled had no settings control. Adds a /config toggle that
refreshes runtime state (re-runs initContextCollapse) so it applies without a
restart.
* fix(context-collapse): clear already-committed staged spans; harden config toggle
After projecting committed collapses before draining, a span present in both the
commit log and the staged snapshot (a restore whose snapshot predates the
matching commit write) could not be drained — projectView had already removed
its messages — so it lingered in stagedQueue and distorted spawn/overflow
checks. drainStaged now drops staged spans that are already committed and syncs
the snapshot. Adds a regression covering the committed+staged overlap restore.
Also wraps the /config context-collapse refresh in try/catch so a failed
require/init can't crash the settings UI, and lists the toggle in the
save-and-close change summary like the neighboring compaction settings.
* fix(context-collapse): re-sync runtime state on config cancel
The context-collapse toggle's onChange refreshes the module-level
enabled/armed cache via initContextCollapse(). The revert path restored
the config key on disk but left that cache untouched, so enabling the
toggle and then pressing Escape kept collapse active for the rest of the
session. Re-init context collapse after the global config snapshot is
restored so cancel fully reverts runtime state.
* fix(context-collapse): keep collapsed summaries visible to the model
projectView and drainStaged replaced an archived span with a system
informational placeholder, but normalizeMessagesForAPI filters out every
system message that is not a local command. So once a collapse committed,
the next model request lost both the archived messages and the
<collapsed> summary meant to stand in for them, defeating the feature.
Mark the placeholder with isCollapseSummary and let it take the same
model-input path as local-command system messages (converted to a user
message), so the summary survives normalization. Added a regression that
runs the projected view through normalizeMessagesForAPI and asserts the
summary is still present.
* fix(context-collapse): avoid competing snapshot write after drain
After an immediate post-spawn drain, drainStaged(messages, true) starts
its own persistCommits().then(persistSnapshot) chain to guarantee commit
durability before the snapshot stops listing the staged spans. The
unconditional await persistSnapshot() that followed could win that race
and persist a snapshot with no staged spans before the commits landed,
reopening the crash window that drops collapses on restore. Only persist
directly when nothing was drained.
* fix(context-collapse): fall back, keep summaries non-snippable, gate /context
Three review findings:
- Suppress autocompact and the blocking preempt only when collapse holds a
real committed/staged reduction, not on mere enablement. Adds
hasActiveReduction(); a first over-threshold turn where spawnCtxAgent cannot
produce a span (getLastCacheSafeParams() still null) now falls back to
autocompact/blocking instead of sending an oversized transcript.
- Preserve isMeta when converting a collapse-summary placeholder to a user
message in normalizeMessagesForAPI, so the HISTORY_SNIP sweep cannot tag the
only replacement for an archived span as snippable.
- Gate the two /context projectView calls on isContextCollapseEnabled(), so a
disabled session does not under-report token usage from a lingering commit
log while the API receives the full transcript.
Adds regressions for hasActiveReduction and for the summary surviving
normalization as a non-snippable meta message.
* fix(context-collapse): scope collapse to the main thread that owns the store
The collapse store (commitLog/stagedQueue) is module-level and shared by
in-process subagents (agent:*) and the ctx-agent (marble_origami), which
run in the same process but do not own the main transcript.
applyCollapsesIfNeeded only skipped marble_origami, so a subagent could
stage or commit a span, flip the global hasActiveReduction(), and make
the next main-thread turn suppress autocompact and the blocking
prompt-too-long preempt while projectView() no-ops against the main
messages, sending an oversized transcript to the API.
Add isMainThreadSource() and gate both application (applyCollapsesIfNeeded,
isWithheldPromptTooLong, recoverFromOverflow) and fallback suppression
(autoCompact shouldAutoCompact, query collapseOwnsIt) to the owning
thread. Subagents now autocompact and preempt their own oversized turns
normally and never mutate the shared store.
Also adds the staged-only hasActiveReduction regression CodeRabbit
requested.
* fix(context-collapse): persist archived count so resumed stats stay accurate
restoreContextCollapseState rebuilt each commit with an empty archived
list, and getStats summed that list, so after a resume /context, the
context visualization, the token warning, and ctx_inspect reported
'N spans summarized (0 messages)' even though projectView was actively
removing the archived spans. The persisted-entry docstring claimed
projectView lazily refills the archive, but it only splices by boundary
uuid and never does.
The archived messages are never read back (only their count fed
getStats), so replace the per-commit Message[] with a persisted
archivedCount. It is written with each commit and restored on resume;
pre-field sessions restore as 0. getStats now reports the same figure
live and after resume.
* fix(context-collapse): keep collapse summary non-snippable across user merge
Preserving isMeta on the system->user conversion was not enough: when the
collapsed span ends right before the next user turn, normalizeMessagesForAPI
merges the summary into that real user message. Under HISTORY_SNIP
mergeUserMessages clears isMeta whenever an operand is real user content and
keeps the real turn's uuid, so the combined block — which carries the only
<collapsed> replacement for the archived span — got a snip id and the model
could queue it for removal.
Carry an isCollapseSummary marker onto the converted user message and through
mergeUserMessages (either operand), strip any snip id already baked into the
real turn when the merge absorbs a summary, and skip such blocks in
appendMessageTagToUserMessage. The merged block stays non-snippable
regardless of merge direction or isMeta being cleared.
* fix(context-collapse): preserve collapse marker on split, drop empty snip blocks
normalizeMessages split path now forwards isCollapseSummary so an array-backed
collapse summary keeps its non-snippable marker across API normalization.
stripSnipTagsFromContent drops a text block whose only content was the snip
marker, so the merge recovery path no longer emits an empty text block.
|
||
|
|
650fae952d |
fix(sdk): make stub-leak detection TDZ-safe + defer to next microtask (#1287) (#1398)
* fix(sdk): make stub-leak detection TDZ-safe + defer to next microtask (#1287) `bun run scripts/start-grpc.ts` crashed at startup with: ReferenceError: Cannot access 'QueryEngine' before initialization. at detectStubLeaks (src/entrypoints/sdk/index.ts:29:33) at src/entrypoints/sdk/index.ts:47:1 The detector ran at module-load time and read each critical import directly. When the start script's circular-import chain reached the SDK barrel before `QueryEngine.js` had finished initializing its own export bindings, the QueryEngine reference at line 29 hit the temporal dead zone and threw. Stub-leak detection is meant to catch `__stub: true` markers from the esbuild plugin — TDZ is a different bug class (an uninitialized binding can't carry `__stub`), so the detector should treat the access failure as 'nothing to check here' rather than crashing the entire SDK entry. Two changes: 1. Wrap each import read in safelyAccess(() => binding) so a TDZ ReferenceError on one returns undefined and the loop continues. Real stub markers still surface as the explicit SDK init error. 2. Defer detectStubLeaks() from module-load to queueMicrotask, so every same-tick init in the circular chain (start-grpc.ts → SDK index → QueryEngine → ... → SDK index) completes before we read bindings. Microtask runs before any actual SDK usage, so a real stub leak still surfaces well before the first query() call. Tests (3): SDK barrel imports without throwing, anti-regression on real __stub: true bindings, TDZ-shaped access returns undefined. * test(sdk): exercise the real stub-leak detector with stubbed fixtures (#1287) The regression test asserted only that a local object literal had __stub === true and re-implemented safelyAccess inline, so it never ran the real detector: removing queueMicrotask(detectStubLeaks), dropping the loop, or swallowing the __stub case would all still pass. Split the detection primitives (safelyAccess + the critical-import scan) into src/entrypoints/sdk/stubLeakDetection.ts and have the SDK entry point import them. The test now feeds stub-shaped fixtures through the real checkCriticalImportsForStubs / safelyAccess and asserts: a real __stub: true binding throws the explicit SDK init error; non-stub modules pass; a TDZ ReferenceError is tolerated (skipped) without crashing; a stub behind a skipped TDZ access is still caught; and the SDK barrel import never throws on its own load. Detector runtime behavior is unchanged. |
||
|
|
c74397cd2f | chore(gitignore): ignore local worktree directories (#1681) | ||
|
|
b8c7c3bfac |
feat(memory): add memory.autoWrite alias for autoMemoryEnabled (#1326) (#1396)
* feat(memory): add memory.autoWrite alias for autoMemoryEnabled (#1326) The attribution half of #1326 was fixed via #1335 (merged 2026-05-26). The memory half — '[memory writes should be] explicit and configurable' with the exact shape `memory.autoWrite` requested by the issue — remains. Rather than parallel-tracking a new key, alias `memory.autoWrite` to the existing `autoMemoryEnabled` opt-out and document the relationship. Either key opts out; when both are set, the more restrictive (false) value wins so a parent-scope opt-out can't be silently re-enabled by a narrower memory.autoWrite: true. The new `memory` namespace is intentional — future opt-in fields (approval gates, etc.) can be added under it without claiming a new top-level key each time. - types.ts: add `memory.autoWrite` to the settings schema; cross-link to autoMemoryEnabled in the description. - paths.ts isAutoMemoryEnabled: read both keys; opt-out wins on conflict; default unchanged (enabled). - paths.test.ts (new): pins default, both opt-out paths, both opt-in paths, opt-out-wins-on-conflict in both directions, env-var still overrides settings. Tests 7/7 green. Default behavior unchanged — this is purely an additive discoverable alias for governance / regulated / client- sensitive repos that prefer the namespaced shape called out in the issue. * fix(memory): evaluate autoWrite opt-out across raw settings sources (#1326) isAutoMemoryEnabled() read the already-merged settings object, so source precedence had already collapsed same-key values before the "false wins" rule applied: a lower-priority memory.autoWrite/autoMemoryEnabled: false opt-out was silently overwritten by a higher-priority true, re-enabling auto-memory against the stated governance guarantee. Evaluate the opt-out across the raw per-source settings instead, via getEnabledSettingSources() + getSettingsForSource() (per-source cached, so the hot path stays cheap). A single false in any source now wins, so a parent-scope opt-out cannot be re-enabled by a narrower scope flipping the key to true. Test now drives per-source fixtures and covers the cross-source precedence case (lower-priority false beats higher-priority true) for both keys. * test(memory): stop the autoWrite test leaking settings mocks across files The previous test mock.module()'d both settings.js and constants.js. bun's mock.restore() does not undo mock.module(), so the constants.js stub leaked into later serial test files and broke flagSettings.test.ts (its cache-busted settings import still resolved the mocked getEnabledSettingSources). Drive the real getEnabledSettingSources() via setAllowedSettingSources() instead of mocking constants, stub only getSettingsForSource, and re-register the real settings module after each test so nothing leaks. Coverage is unchanged (per-source fixtures + cross-source precedence cases). |
||
|
|
8f88608055 |
test(file-suggestions): stop cross-spawn mock leaking into later suites (#1667)
fileSuggestions.test.ts installs a cross-spawn mock via mock.module, which bun does NOT undo on mock.restore() — it persists process-wide. The mock's interception was gated on a closure captured at install time, so after this suite the persisted mock kept returning a fake child (with no kill()) for any git command. Test files run sequentially in one process, so a later suite's real `git ls-files` (e.g. /lsp recommend's filesystem-scan fallback) hit the fake child, hung to its 5s timeout, and crashed on child.kill() — an order-dependent failure in smoke-and-tests. Gate the interception on a module-level activeSpawnScenario that is set only while one of this suite's spawn-scenario tests runs and cleared in afterEach, so the persisted mock falls through to the real spawn afterward. Also give the fake child a kill() that emits close, so any stray caller terminates cleanly instead of throwing. Verified the full src suite is green across repeated runs (was intermittently red on the /lsp filesystem-scan test). |
||
|
|
5c0e6612c2 |
chore(main): release 0.19.0 (#1596)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>v0.19.0 |
||
|
|
7be9dce8ef |
fix: vision handling for OpenAI-compatible models (#1663)
* Fix vision handling for OpenAI-compatible models Add route-aware vision capability checks for image reads so registered non-vision models get an actionable refusal before sending image content. Classify provider-side image/text errors as canonical vision_not_supported responses, preserve image-only tool results for OpenAI-compatible shims, and strip rejected images from retry messages. Add focused coverage for Xiaomi MiMo/OpenGateway route collisions, canonical errors, shim image handling, and the Read prompt. * Address vision review findings Move the Read tool vision gate before the UNC no-I/O early return so UNC image paths cannot bypass non-vision model checks. Add direct FileReadTool.validateInput coverage for non-vision denials, provider override/env precedence, and UNC image paths. Add the missing OPENAI_BASE_URL exclusion assertion for the Xiaomi MiMo canonical error path. * Isolate vision gate tests from provider env Clear OPENAI_BASE_URL and OPENAI_API_BASE before each FileReadTool vision-gate test so full-suite provider tests cannot leak route state into these cases. * Fix vision gate test full-suite isolation Import FileReadTool and prompt with a cache-busted module id so compact.test's process-global mock cannot replace validateInput during test:full. Invoke validateInput directly instead of optional chaining, matching the review finding and making missing exports fail clearly. * Lock vision prompt env mutations Acquire the shared mutation lock before mutating OPENAI_BASE_URL and OPENAI_API_BASE in the FileReadTool vision prompt tests, and release it after restoring the environment. |
||
|
|
bac74aafee |
fix: Ollama max output token override (#1659)
* Fix Ollama max output token override Allow unknown integration models without runtime maxOutputTokens metadata to honor CLAUDE_CODE_MAX_OUTPUT_TOKENS above the Anthropic 64k fallback while still capping at the provider context window or OpenAI-compatible fallback context window. Update the max-output error copy for third-party providers, add regression coverage for issue #1604, and ignore generated Python/pytest cache artifacts. * Use standard pytest cache ignore pattern Replace the non-standard pytest-cache-files pattern with pytest's default .pytest_cache directory ignore entry. |
||
|
|
0c45e16f18 |
feat(config): add compactModel option to use a separate model for compaction (#1445) (#1629)
* feat: add compactModel config option to use a different model for compaction When set and different from mainLoopModel, the forked-agent prompt-cache-sharing path is bypassed (guaranteed cache miss with different models) and the streaming fallback uses compactModel for the API call instead. Closes #1445 * feat(config): expose compactModel in /config TUI and add compactModel test coverage Addresses review feedback on #1629: - Surface compactModel as a managedEnum setting in the /config screen, following the teammateDefaultModel pattern (submenu + ModelPicker). - Add a compact.test.ts case covering the compactModel !== mainLoopModel path: cache-sharing is skipped and the streaming compaction fallback routes model/maxOutputTokensOverride to compactModel. * fix(compact): use compactModel for tool-search check and add no-op guard Addresses round-2 review feedback on #1629: - compact.ts: pass compactModel ?? mainLoopModel to isToolSearchEnabled so the tool-search capability check matches the model actually used for streaming compaction (not always mainLoopModel when a compact model is set) - Config.tsx: mirror the teammateDefaultModel no-op guard — return early when compactModel is unset and the picker confirms null (no-op selection) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(compact): normalize compactModel comparison in no-op guard Compare globalConfig.compactModel ?? null against the picker's selection so re-confirming the current value (including explicit "Default"/null when a model was previously set) is treated as a no-op instead of marking settings dirty. * fix: resolve compactModel alias to full model ID before API calls ModelPicker stores alias strings (e.g. 'sonnet') directly in globalConfig.compactModel. Compact reads that value and must call parseUserSpecifiedModel() to expand it to the canonical model ID before comparing against mainLoopModel or sending to the API. Two read-sites in compactConversation and streamCompactSummary are both fixed. Test updated so the 'skips cache-sharing' case uses the resolved model ID (legacy claude-opus-4-1 remaps to current default) and a new test verifies alias expansion end-to-end. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
c3db79832b |
fix: sandbox temp dir fallback (#1662)
* Fix sandbox temp dir fallback Probe Claude temp directories before returning them and fall back through platform temp and config-home temp paths when the primary temp base is inaccessible. Use the resolved Claude temp dir for sandboxed shell cwd tracking and TMPDIR/CLAUDE_TMPDIR propagation so the sandbox allowlist, Bash, and PowerShell providers agree on the writable temp path. Update @anthropic-ai/sandbox-runtime to 0.0.55 and refresh bun.lock. Validation: bun install passed after escalation; bun run build passed; python -m pytest -q python/tests passed; bun run typecheck:type-tests passed; git diff --check passed. bun run check still reports full-suite order/global-state failures; focused reruns of the reported failing files passed with a dummy ANTHROPIC_API_KEY. bun run typecheck has pre-existing unrelated repo-wide strictness failures; security:pr-scan fails before scanning on mergeBase.stderr. * Fix PR typecheck and read-only temp fallback Handle EROFS as an inaccessible filesystem error for sandbox temp fallback behavior. Add narrow type annotations and inference fixes so the stricter typecheck job passes. |
||
|
|
a36ef463ce |
docs(readme): add social links and clarify license line (#1660)
- Add Discord (discord.gg/k68zFR6AcB) and X (x.com/gitlawb) as shields.io badges in the top badge row and as descriptive links in the Community section. - License section now notes contributor modifications are MIT while the derived Claude Code remains Anthropic's, with a "See more" link to LICENSE. Co-authored-by: OpenClaude <openclaude@gitlawb.com> |
||
|
|
241d52af47 |
fix(gitDiff): keep hunk content lines beginning with -- or ++ (#1646)
The metadata-skip block in parseGitDiff matched line.startsWith('---')
and line.startsWith('+++') on every line, including lines inside a hunk.
A removed line whose content starts with '--' becomes the diff line
'---...', and an added line whose content starts with '++' becomes
'+++...'; both were treated as file-header lines and dropped, so the
rendered diff silently lost real changes.
These header markers only appear in the file preamble before the first
@@ hunk header, so the skip block is now gated on !currentHunk. Inside a
hunk, +/-/space lines are content and are kept.
Adds parseGitDiff tests covering the dropped-content case, the regression
that header lines are still skipped, and a normal hunk.
|
||
|
|
bd3ad89dd7 |
fix(security): bundle real sandbox runtime in open CLI (#1641)
* fix(security): bundle real sandbox runtime in open CLI * test(sandbox): cover fail-closed runtime diagnostics * fix(sandbox): report doctor inspection failures |
||
|
|
7c034c5a62 |
feat: add redacted diagnostic issue reports (#1647)
* feat: add redacted diagnostic issue reports * fix: address diagnostic report review feedback * fix: report Codex runtime diagnostics accurately |
||
|
|
b036e9fa7c |
fix: startup provider validation fallback (#1658)
* fix startup provider validation fallback * test startup provider behavior |
||
|
|
d8dbf274b4 |
chore(runtime): align Node.js minimum version (#1644)
* chore(runtime): align Node.js runtime requirements * test(runtime): cover prefixed Node versions * fix(runtime): check node executable in doctor |