Wall-clock corrections can otherwise manufacture an immediate timeout or postpone an already-scheduled one. Keep lifecycle timestamps in wall time while measuring deadline state and elapsed duration from a monotonic clock.\n\nRefs #1830
* fix(session): make transcript replacements crash-safe
Complete transcript rewrites could truncate live JSONL files before preserved data was durable, risking unrecoverable resume history after an interrupted write. Commit replacements through exclusive sibling temp files and serialize them with all transcript append paths so readers observe either the old file or the complete replacement.
* fix(session): preserve concurrent transcript updates
Abort tombstone commits when the scanned transcript changes before replacement, and keep existing local history when remote foreground hydration returns no entries. Harden the associated portability, option coverage, queue timing, and diagnostics.
* test(session): match hydration reader signature
Pass the explicit optional subagent reader in the empty-hydration regression so a fresh TypeScript build sees the complete helper signature.
* fix(session): coordinate transcript writers across processes
Hold a same-directory cooperative lock across transcript replacement and final-line truncation, and make session plus SDK append paths participate. Exercise the post-validation/pre-rename race deterministically so external appends land after the complete commit.
* test(session): provide empty hydration subagent reader
* fix(session): scope transcript lock ownership
Separate async and synchronous lock ownership so unrelated sync appends cannot bypass an in-flight replacement. Route aliased in-process appends through the queue, propagate lock compromise through AbortSignal, and cover both symlink-alias and rename-boundary races.
* fix(mcp): serialize OAuth and XAA refresh across processes
Normal OAuth refresh, reactive 401 recovery, and silent XAA exchange can otherwise race shared secure-storage writes between processes. Coordinate them on one server-scoped lock and re-read storage so waiters reuse persisted winners.
* fix(mcp): harden refresh follow-up paths
Use asynchronous cache-bypass reads on request paths while preserving the adjacent final record merge and write. Make the XAA concurrency fixtures independent of module import order and extend abort, redaction, and retry coverage.
* fix(mcp): honor aborts after credential reads
Check the active cancellation signal after asynchronous secure-storage reads so fresh-token fast paths cannot return credentials to an aborted request. Cover cancellation while a cache-bypassing read is pending.
* perf(cli): enable Node module compile cache
Warm CLI invocations spend substantial time compiling the bundled ESM entrypoint. Enable Node's optional on-disk compile cache only in the process that imports the bundle, while preserving early Node 22 compatibility and making cache failures non-fatal.
Add deterministic launcher coverage, packaging checks, and a reproducible benchmark procedure so the startup benefit can be measured without flaky CI thresholds.
* fix(ci): isolate minimum Node launcher check
The full validation suite depends on knip and oxc-parser behavior unavailable in Node 22.0.0. Keep full CI on the active Node 22 line and exercise the declared runtime floor in a dedicated build-and-launch job.
* fix(benchmark): harden startup measurements
Keep environment setup outside the timed process window, document the API's Node 22.8 floor, and preserve completed benchmark results when git metadata is unavailable.
* test(cli): verify compile cache disable behavior
Pair NODE_DISABLE_COMPILE_CACHE with a temporary cache directory and assert that supported Node releases leave it empty while preserving normal launcher output.
* fix(input): preserve text in DEL-coalesced chunks
Some terminal transports deliver replacement input as raw DEL bytes and printable text in one read. The raw-DEL workaround previously applied only the deletions and returned, dropping the replacement text and leaving same-event cursor and mode state stale.
Process filtered chunks in source order through the existing cursor semantics, preserve coalesced submission and Vim state, and cover grapheme, token, filter, mode, and batching cases.
* test(input): harden DEL regression coverage
* test(input): clean up harnesses after timeouts
* fix(input): preserve coalesced consumer state
* fix(input): synchronize coalesced mode state
Scale HISTORY_SNIP context-efficiency nudges with the active model window so large-context sessions do not get prompted after fixed low token growth.
Keep existing reset behavior and add focused regression coverage for custom intervals and model-aware attachment gating.
* fix(compaction): respect disabled microcompact setting
Skip automatic query-loop microcompact when the message-count compaction threshold is explicitly set to off, while preserving default, numeric, and explicit compact behavior.
* test(query): isolate auto-compact config regression
* test(query): type auto-compact deps fixture
* feat(cli): add headless heartbeat for print mode
* fix(cli): harden heartbeat validation and predicates
* fix(cli): align print heartbeat phases
* fix(cli): keep heartbeat payloads schema-valid
* fix(cli): delay stream-json heartbeat until drain
* test(sdk): cover heartbeat placeholder identifiers
* fix(cli): clamp heartbeat durations
* fix(cli): ignore file persistence final events
* test(cli): cover post-turn final filtering
* fix(cli): harden headless heartbeat follow-up
Export the heartbeat SDK message type from generated core types.
Keep heartbeat cleanup paired with setup and streaming failures, and cover timing/count edge cases with focused regression tests.
* test(sdk): exercise generated heartbeat types
Expose the SDK type generator as a pure helper so tests compare fresh output with the checked-in generated artifact.
* fix(scripts): canonicalize sdk type generator entrypoint
Compare real paths for direct script execution so symlinked invocations still run the generator.
* test(sdk): harden generator import coverage
Normalize generated type freshness checks across line endings and keep the SDK type generator import-safe for non-file entrypoints.
* test(sdk): assert generator import has no write side effects
Snapshot the generated SDK type artifact around the non-file import regression so importing the generator cannot silently rewrite the committed output.
* fix(resume): tolerate malformed legacy attachment records
* test(resume): allow resume hook messages in recovery test
* fix(resume): use validated attachment for skill listing
* 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
* 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
* 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().
* chore(query): add tool-pairing diagnostics
Add a pure validator for tool_use/tool_result pairing issues and feed phase, query source, model, provider, and agent context into the existing pre-API repair log. Keep ensureToolResultPairing behavior intact while making future repair logs identify missing, orphaned, duplicate tool_use, and duplicate tool_result cases.
* fix(query): complete pairing diagnostics coverage
Address CodeRabbit review by detecting server-side tool use blocks without matching in-message results and by making pairing validation lazy so it only runs after the repair path has actually mutated messages.
* fix(tokens): fallback when provider lacks countTokens
* test(tokens): isolate shim fallback coverage
Avoid process-wide api client mocks in token estimation tests by exercising the count-token dispatch helper directly. Add non-empty tool coverage for the rough fallback path so the local overhead remains covered.
* fix(typecheck): expand cachedMicrocompact stub exports
The cachedMicrocompact module is a feature-gated stub that only exported
3 functions, but microCompact.ts expected 10+ exports including types and
state management functions.
Changes:
- Add missing type exports: CachedMCState, CacheEditsBlock, PinnedCacheEdits, CachedMCConfig
- Add missing function stubs: createCachedMCState, markToolsSentToAPI, resetCachedMCState, registerToolResult, registerToolMessage, getToolResultsToDelete, createCacheEditsBlock
- Add null guard in cachedMicrocompactPath for config
- Add comprehensive tests for stub behavior
Resolves 15 typecheck errors in microCompact.ts:
- TS2694: Namespace has no exported member (10 errors)
- TS2339: Property does not exist (5 errors)
- TS18047: possibly null (2 errors)
Testing:
- bun test src/services/compact/cachedMicrocompact.test.ts: 7/7 pass
- bun test src/services/compact/: 61/61 pass
- bun run smoke: pass
- Typecheck errors: 697 → 682 (-15)
* fix(typecheck): address CodeRabbit review feedback
- Add missing fields to CachedMCConfig type: enabled, supportedModels,
systemPromptSuggestSummaries (used by prompts.ts and claude.ts)
- Replace silent fallback with explicit error throw in cachedMicrocompactPath
to enforce the invariant that isCachedMicrocompactEnabled() === true implies
getCachedMCConfig() !== null, preventing potential recursion in future
non-stub implementations
* test: prevent CI state leaks
* fix: resolve 28 typecheck errors in openaiShim.ts
Add null guards for nullable `reader`, `response`, and `responsesResponse`
variables, and use type assertions to bridge Node vs Web ReadableStream
type mismatches in stream processing helpers.
* fix(typecheck): resolve 17 errors in agentSdkTypes.ts
- Add @ts-expect-error for settingsTypes.generated.js (generated at build time)
- Fix type imports: redirect 5 types from ./sdk/runtimeTypes.js to ./sdk/shared.js
- Remove 11 unused type imports that don't exist (AnyZodRawShape, InferShape, etc.)
* fix(typecheck): resolve 26 errors in openaiShim.ts — nullable guards, ReadableStream types
* fix(typecheck): resolve 23 errors in messages.ts + groupToolUses.ts
MessageWithoutProgress resolved to `never` because all message types are
`any` stubs, making `Exclude<any, any>` = `never`. Widen types and use
boolean wrappers to avoid type-predicate narrowing. Add missing return
in getToolUseID switch statement.
* fix(typecheck): resolve 14 errors in toolExecution.ts — fix never[] inference
* fix(typecheck): resolve type errors in claude.ts
- Cast nested block params to BetaContentBlockParam for SDK type union mismatch
- Add missing CACHE_EDITING_BETA_HEADER constant to betas.ts
- Type-assert getCachedMCConfig() return for supportedModels access
- Add missing imports: getContextWindowForModel, COMPACT_MAX_OUTPUT_TOKENS, getSdkBetas
- Fix model variable reference to use options.model in compact context
- Add optional signature property to ConnectorTextBlock type
* ci: re-trigger checks
* fix: address CodeRabbit review feedback
- Throw error instead of silent return when response body is not readable
- Clamp hybrid context budget to non-negative floor (Math.max(0, ...))
- Remove unused isResult wrapper in messages.ts
* chore(tests): fix flaky and environment-dependent unit tests
- Mock fs.existsSync in geminiAuth tests to prevent real ADC reads
- Isolate runtimeMetadata tests from local environment variables
- Add shared mutation lock to sessionStorage tests to prevent env race conditions
* test: address environment cleanup review
* test(autoCompact): guard temp-dir cleanup against setup-time failures
If beforeEach throws before tempDir is assigned (e.g. lock acquisition or
mkdtempSync fails), afterEach's unconditional rmSync(tempDir) threw a
TypeError and masked the original failure. Type tempDir as string | undefined
and skip removal when unset, mirroring the savedAutoCompactEnabled reset.
Addresses CodeRabbit review on PR #1582.
Co-Authored-By: OpenClaude <openclaude@gitlawb.com>
---------
Co-authored-by: Kevin Codex <kevin@gitlawb.com>
Co-authored-by: OpenClaude <openclaude@gitlawb.com>