707 Commits

Author SHA1 Message Date
Alex Newman
4c4d1e573f chore: bump version to 13.10.2 2026-07-05 15:46:39 -07:00
Alex Newman
c275b340c8 fix(worker): graft worker-script resolver identity + IPv6 + worktree gitdir
Grafted from the two plan branches, minus their regressions:
- worker-utils: cache-first resolveWorkerScriptPath so hook/MCP/CLI/restart
  launches converge on one worker bundle (stops cache-vs-marketplace version
  skew); + CLAUDE_MEM_WORKER_SCRIPT_PATH override; + formatHostForUrl IPv6
  bracketing in buildWorkerUrl.
- HealthMonitor: IPv6-bracket the worker host in health/readiness fetches.
- worktree: resolve a relative gitdir against the .git file's dir before
  matching worktrees/, so a relative 'gitdir:' entry is not misread as a
  non-worktree.

Excluded: c78b8bf8's stale-socket recovery (dead code — SO_REUSEADDR means
TIME_WAIT never raises EADDRINUSE) and the fd1b5221 submodule re-keying.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:14:02 -07:00
Alex Newman
2e15df0e8d build: regenerate plugin bundles from integrated source
Reconciles plugin/scripts/*.cjs, plugin/sqlite/* against the cherry-picked
source (worker-host, spawn shims, marketplace repair, sqlite/settings,
sqlite runtime modules, codex Windows hooks, SDK proxy env). Excludes the
observer-truncation and project re-keying regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:09:12 -07:00
Alex Newman
22e4325ce2 fix: harden sqlite migrations and settings writes
(cherry picked from commit b20024b772)
2026-07-05 15:06:27 -07:00
Alex Newman
83dd59256f fix: centralize windows spawn shims
(cherry picked from commit 92997115e5)
2026-07-05 15:06:27 -07:00
Alex Newman
41113d3454 fix: use health oracle and configured worker endpoint
(cherry picked from commit 3695d11601)
2026-07-05 15:06:12 -07:00
Alex Newman
36cb8fcb49 chore: bump version to 13.10.1 2026-07-03 21:52:49 -07:00
Alex Newman
47d14c1747 fix: preserve empty-string additionalContext in Codex hook output
codexAdapter.formatOutput used a truthy check on additionalContext,
silently dropping the key whenever it was an explicit empty string
(HookResult types it as a required string, not optional). The new
SessionStart no-op fallback from buildNoOpResult('context') hit this
exactly, emitting hookSpecificOutput without additionalContext at all.
Switch to a typeof check so the key is preserved whenever the caller
set it, matching the type contract.

Addresses review feedback on #3127.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YMT5iCF1kfikq7fetR6NF
2026-07-03 21:44:09 -07:00
Alex Newman
62693445b6 fix: emit valid SessionStart hookSpecificOutput on hook no-op fallback
hook-command.ts fell back to a bare {continue:true, suppressOutput:true}
whenever a hook errored before its handler ran (missing session_id,
invalid cwd, missing transcript path). Codex's strict SessionStart
validator rejects that shape outright ("hook returned invalid session
start JSON output"), since `context` is the SessionStart handler on every
platform. buildNoOpResult() now attaches a minimal valid
hookSpecificOutput for that path so Codex's startup hook no longer fails.

Closes #2947
Closes #2972

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YMT5iCF1kfikq7fetR6NF
2026-07-03 21:35:15 -07:00
Alex Newman
1993559540 chore: bump version to 13.10.0
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YMT5iCF1kfikq7fetR6NF
2026-07-03 19:17:02 -07:00
Alex Newman
9cbd3f8203 fix: drop dead SessionEnd hook, wire antigravity-cli into npx-cli
Two issues from automated PR review, both verified against the code
before fixing:

- SessionEnd was mapped to 'session-complete', but that internal event
  has had no handler since a past release deliberately removed it (the
  worker now self-completes via its SDK-agent generator's finally-block,
  per CHANGELOG.md:777). The mapping only looked "confirmed live" because
  stale pre-cleanup config was still sitting in ~/.gemini/settings.json.
  Dropped back to the correct 7-event map; the deleted Gemini CLI compat
  test had already asserted this exclusion, which the new test missed.
- `npx claude-mem antigravity-cli install|status|uninstall` was
  unreachable (no case in src/npx-cli/index.ts's dispatcher, though
  worker-service.cjs's Bun-runtime CLI had it). Not a regression --
  gemini-cli never had this either -- but cheap to fix while here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YMT5iCF1kfikq7fetR6NF
2026-07-03 15:57:33 -07:00
Alex Newman
ec783e87e4 fix: write Antigravity rules context to home dir, not cwd (Phase C)
The hands-on install/hook verification gate caught this live: running
`claude-mem install --ide antigravity` from this repo wrote
.agents/rules/claude-mem-context.md into the repo's working directory
instead of ~/.agents/rules/, the actual location Antigravity CLI reads
project-independent rules from. Carried over unnoticed from the old
MCP-only ANTIGRAVITY_CONFIG, which had the same process.cwd() bug.

Verified end-to-end after the fix: real install run, hooks fire
correctly (context + observation events), MCP tools reachable via both
registered config paths, GEMINI.md and rules-file context injection
both confirmed live on this machine.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YMT5iCF1kfikq7fetR6NF
2026-07-03 15:39:32 -07:00
Alex Newman
173ebb26a9 feat: add full Antigravity CLI support (Phase B)
Adds hooks, dual MCP server registration, and GEMINI.md/rules context
injection for Antigravity CLI (Gemini CLI's official successor per
Google, May 19 2026), at feature parity with the removed Gemini CLI
integration. Built and verified against a real live Antigravity CLI
install (agy v1.0.16):

- Hooks share Gemini CLI's proven ~/.gemini/settings.json + 8-event map
  (SessionStart, BeforeAgent, AfterAgent, BeforeTool, AfterTool,
  Notification, PreCompress, SessionEnd), confirmed live on disk.
- MCP registration dual-writes ~/.gemini/antigravity/mcp_config.json
  and ~/.gemini/config/mcp_config.json (both real paths observed in use).
- Detection checks for the `agy` binary alongside the existing
  ~/.gemini/antigravity directory check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YMT5iCF1kfikq7fetR6NF
2026-07-03 15:34:05 -07:00
Alex Newman
26fe40a6c1 feat: remove Gemini CLI host integration (Phase A)
Google deprecated Gemini CLI's free/individual tier in favor of Antigravity
CLI (announced May 19, 2026, cutoff June 18, 2026). Removes claude-mem's
Gemini CLI adapter, installer, IDE-detection entry, and docs.

The separate Gemini LLM/observation provider (CLAUDE_MEM_GEMINI_API_KEY,
GeminiProvider.ts, GeminiObservationProvider.ts) is untouched — that's a
different, still-supported Google product.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YMT5iCF1kfikq7fetR6NF
2026-07-03 14:51:33 -07:00
Alex Newman
fd07bfa564 chore: bump version to 13.9.3 2026-07-02 22:22:10 -07:00
Alex Newman
2a0a407340 chore: remove over-engineering findings from repo-wide ponytail audit (wave 1)
Cuts verified dead code, duplication, and speculative abstractions across
14 disjoint areas of the tree: sqlite layer, worker HTTP routes, search
pipeline, server, chroma sync, viewer UI, npx-cli, MCP server, shared/utils,
telemetry/infra, integrations, scripts, tests, and stale plans/evals docs.

Also unifies the Chroma search pipeline onto SearchOrchestrator/strategies
and ports dual-project (merged_into_project) scoping plus dateRange
filtering into ChromaSearchStrategy, which corpus builds need but had
silently lost.

Full audit trail: 65-agent verification workflow wf_160a7862-1a6, 14-agent
execution wf_c7e48c0f-164, 6-agent fixup wf_865f86a4-c6b.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 20:49:27 -07:00
Alex Newman
39dd77d9e3 fix: eliminate all 331 error-handling anti-patterns detected by scanner (#3119)
Drive scripts/anti-pattern-test/detect-error-handling-antipatterns.ts to
zero issues across the entire src/ tree (331 -> 0):

- NO_LOGGING_IN_CATCH (124): every silent catch now logs the full error
  object via logger/console, or carries an explicit documented override
- PROMISE_EMPTY_CATCH (6): empty .catch(() => {}) handlers in the SDK
  cleanup paths now log the swallowed cleanup failure
- GENERIC_CATCH (78): catches discriminate error types via instanceof
  Error normalization instead of blind `as Error` casts
- LARGE_TRY_BLOCK (115): oversized try blocks narrowed to the throwing
  statements or extracted into named helpers, semantics-preserving
- CATCH_AND_CONTINUE_CRITICAL_PATH (6) + ERROR_STRING_MATCHING (2):
  fixed or documented with [ANTI-PATTERN IGNORED] justifications

297 real fixes, 38 documented overrides (each with a specific technical
reason and explicit recovery). No behavior changes: swallowed errors
remain non-throwing, only visible now.

Verified: detector exits 0, tsc --noEmit clean (both tsconfigs),
2292 tests pass / 0 fail. Includes rebuilt plugin artifacts.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 13:09:09 -07:00
Alex Newman
ed97d4e490 chore: bump version to 13.9.2 2026-06-30 21:52:15 -07:00
Alex Newman
29af028403 fix(providers): remove client-side context truncation ("second system")
The OpenAICompatibleProvider.truncateHistory() sliding-window dropped
conversation messages based on a hardcoded 20-message cap and a 100k
"safety" token limit — a client-side context manager layered on top of
the provider's own context window. In practice it fired on message count
alone (dropping messages at ~12k tokens, nowhere near the token limit)
and silently corrupted conversation history, mislabeled as "runaway cost"
prevention.

Rip it out entirely. The full history is now sent to the provider, which
owns its own context window.

Removed:
- truncateHistory() + requireNonEmptyToTruncate from OpenAICompatibleProvider
- truncateHistoryForOpenRouter / truncateHistoryForGemini wrappers + constants
- CLAUDE_MEM_{GEMINI,OPENROUTER}_MAX_CONTEXT_MESSAGES / _MAX_TOKENS settings,
  their defaults, and SettingsRoutes validation
- truncation-specific tests; docs + openclaw installer references

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:52:54 -07:00
Alex Newman
482b4f0a15 fix(server): 404 on cross-team/nonexistent project purge
ensureProjectAllowed only checks a key's optional project scope, so a
team-scoped key could call DELETE /v1/projects/:projectId/memory with any
projectId and get 200 { purged: true } with zero counts — misreporting an
unauthorized or nonexistent purge as success. Verify the project belongs to
the team (getByIdForTeam) before purging and 404 otherwise. The underlying
purge was already team-scoped, so no cross-team data was ever deleted; this
fixes the misleading success response. Addresses greptile P1 on PR #3089.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 17:58:38 -07:00
Alex Newman
5bd7da778e chore: build-and-sync after merging ChenglinWei97 server PRs 2026-06-29 17:49:58 -07:00
Alex Newman
f1b8fdb28a chore: bump version to 13.9.1 2026-06-29 16:47:29 -07:00
Alex Newman
4af7daf29c test: stabilize session init after server rename 2026-06-29 16:23:14 -07:00
Alex Newman
aad0d87c55 Merge remote-tracking branch 'origin/main' into release/recovery-2026-06-24
# Conflicts:
#	plugin/scripts/context-generator.cjs
#	plugin/scripts/mcp-server.cjs
#	plugin/scripts/server-service.cjs
#	plugin/scripts/transcript-watcher.cjs
#	plugin/scripts/worker-service.cjs
#	src/cli/handlers/session-init.ts
#	src/servers/mcp-server.ts
#	src/services/sync/ChromaSync.ts
#	tests/hooks/server-client.test.ts
2026-06-29 16:01:54 -07:00
Alex Newman
8d80ca4b15 fix(codex): load startup context through MCP 2026-06-29 15:57:33 -07:00
Alex Newman
8f65bb0860 chore: bump version to 13.9.0
Ships the claude-mem/sdk export (cmem-sdk) — in-process
capture→compress→semantic-search with no HTTP worker or Redis — plus the
server-beta→server runtime rename (back-compat aliases retained).

Includes CMEM-SDK reference docs (docs/public/sdk/cmem-sdk.mdx) and a
correction to sdk.mdx's stale parse-error behavior note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 15:28:24 -07:00
Alex Newman
348d9ee406 feat(recovery): scope memories by platform source 2026-06-29 15:24:59 -07:00
Alex Newman
108c5c65a1 Merge remote-tracking branch 'origin/main' into mutual-aardvark
# Conflicts:
#	plugin/scripts/context-generator.cjs
#	plugin/scripts/mcp-server.cjs
#	plugin/scripts/server-service.cjs
#	plugin/scripts/transcript-watcher.cjs
#	plugin/scripts/worker-service.cjs
#	plugin/ui/viewer-bundle.js
#	src/server/routes/v1/ServerV1PostgresRoutes.ts
#	src/server/runtime/ServerService.ts
#	src/server/runtime/create-server-service.ts
#	src/server/runtime/types.ts
#	tests/server/server-service.test.ts
2026-06-29 15:10:51 -07:00
Alex Newman
ad4bd6f7a6 fix(observer): drop invalid prose and pause on quota 2026-06-29 10:27:11 -07:00
Alex Newman
8702c8f331 fix(chroma): prewarm uvx and harden shutdown 2026-06-29 10:04:51 -07:00
Alex Newman
f42bead214 refactor(sdk): trim over-engineering from cmem-sdk client
Ponytail-review cleanups, behavior-preserving:
- dedup provider instantiation into instantiateProvider() (was duplicated
  across config + env resolution paths)
- reuse ChromaSync.getCollectionName() instead of re-deriving the cm__
  collection name (kills drift risk)
- drop redundant ChromaMcpManager.isHealthy() probe; ensureCollectionExists
  already rethrows when the subprocess can't start
- replace repeated optional-field plumbing with assignDefined() helper
- remove no-op try/catch that re-threw verbatim, and the IIFE event-load dance
- drop defensive seen-Set dedup of unique Chroma doc ids

net -42 lines in src/sdk/index.ts. typecheck + build + SDK import guard +
51 unit tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 21:33:40 -07:00
Alex Newman
4557920565 Merge remote-tracking branch 'origin/main' into mutual-aardvark
# Conflicts:
#	package.json
#	plugin/scripts/context-generator.cjs
#	plugin/scripts/mcp-server.cjs
#	plugin/scripts/server-service.cjs
#	plugin/scripts/worker-service.cjs
#	plugin/ui/viewer-bundle.js
#	src/server/runtime/create-server-service.ts
#	tests/sdk/parse-summary.test.ts
#	tests/sdk/parser.test.ts
2026-06-27 21:20:51 -07:00
Alex Newman
c520c8f2c9 fix(deps): surface dependency health preflight 2026-06-25 15:02:25 -07:00
Alex Newman
f7aa16d60f fix(deps): degrade gracefully when cli deps are missing 2026-06-25 14:21:26 -07:00
Alex Newman
7e32c9ae29 fix(telemetry): replace Bun UUIDv5 dependency 2026-06-25 13:37:48 -07:00
Alex Newman
3fe0725a97 chore: bump version to 13.8.1 2026-06-24 16:34:28 -07:00
Alex Newman
16b2c72d57 fix(codex): repair startup hooks and stale mcp config 2026-06-24 16:32:33 -07:00
Alex Newman
eac30a1570 chore: bump version to 13.8.0 2026-06-21 13:33:31 -07:00
Alex Newman
ebe6133089 feat(telemetry): carry observation volume on rollups so cache-value survives migration (#3017)
* feat(telemetry): carry observation volume on rollups so cache-value survives migration

The context-cache-value, per-user-savings, and observation-type-by-model
metrics were derivable only from the legacy per-occurrence events
(context_injected, session_compressed), which decay to zero as the fleet
upgrades to 13.7.0 and switches to the rollups. The rollups already received
the underlying records — they just didn't aggregate the observation fields.

- observer_turn_rollup: add observations_created (Σ per-turn observation count,
  distinct from the rollup's turn `count`) + summed obs_type_* buckets, so
  cost-per-observation (total_cost_usd / observations_created) and
  observation-type-by-top_model are derivable from the rollup alone.
- context_injected_rollup: add total_observations_injected (cache-reuse count)
  + total_tokens_saved_vs_naive (windowed savings sum).
- scrub.ts: whitelist the three new emitted keys (obs_type_* already allowed;
  deny-by-default whitelist would drop them otherwise).
- docs: correct the rollup field tables — the prior context_injected_rollup row
  documented fields the code never actually emitted.
- tests: assert both new aggregations (167 telemetry tests pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEZpnYz9z4TjKcG19qHFrJ

* build(telemetry): regenerate plugin bundles for rollup observation fields

worker-service.cjs and transcript-watcher.cjs rebuilt via `npm run build` to
bundle the new observation aggregation. Incidental, telemetry-unrelated churn
in the other service/UI bundles was left out to keep the diff meaningful.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEZpnYz9z4TjKcG19qHFrJ

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 13:31:35 -07:00
Alex Newman
a4e16b9345 chore: bump version to 13.7.1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 13:15:56 -07:00
Alex Newman
edc5cf7d59 Ponytail audit (−10.4k lines) + worker-restart hardening + deterministic dependency closure (#3021)
* plan-10 Phase 1: ship deterministic plugin runtime dependency closure

Approach A — commit & ship plugin/bun.lock so the plugin's runtime
node_modules install is deterministic, fixing the recurring
`Cannot find module 'zod/v3'` (#2730).

- align generated plugin zod range to root (^4.4.3) in build-hooks.js
- new scripts/gen-plugin-lockfile.cjs generates plugin/bun.lock as a
  build artifact after build-hooks.js writes plugin/package.json
- track & ship plugin/bun.lock (.gitignore negation, .npmignore, files allowlist)
- install with `bun install --frozen-lockfile --ignore-scripts` at runtime

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10 Phase 2: fail loud at install time on a broken dependency closure

Strengthen verifyCriticalModules to assert each dependency is actually
importable via require.resolve (not merely a directory), and assert the
worker-required zod subpaths resolve: zod/v3, zod/v4, zod/v4-mini.
A partial/stale install now fails `npx claude-mem install` immediately
instead of surfacing later as a Stop-hook `Cannot find module 'zod/v3'`.

Bin-only packages (e.g. tree-sitter-cli, which has no bare-name entry
point) fall back to resolving <dep>/package.json so a healthy install
isn't falsely rejected.

Adds tests/cli/verify-critical-modules.test.ts covering a missing zod/v3
subpath (throws), a complete zod (passes), and a bin-only dep (passes).

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10 Phase 3: clean-room install + import smoke test (#2730 backstop)

Add scripts/smoke-clean-room.cjs and a `smoke:clean-room` npm script.
Against fresh temp dirs (never the repo's node_modules) it:
- copies plugin/, runs `bun install --frozen-lockfile --ignore-scripts`,
  asserts zod, zod/v3, zod/v4, zod/v4-mini resolve, and boots the bundled
  worker asserting no `Cannot find module` — the direct #2730 regression guard;
- `npm pack`s, installs the tarball into a second temp dir, and load-tests
  the published bin entrypoint, warning loudly on any declared main/exports
  target missing from the tarball (latent #2537 gap).

Exits non-zero naming the missing module on any failure; cleans up all
temp dirs and the tarball in a finally.

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10 Phase 4: gate CI and publish on the clean-room dependency closure

- ci.yml: new `clean-room-deps` job (between build and the docker e2e job)
  runs a frozen-lockfile drift check on the committed plugin lockfile, then
  `npm run build` + `npm run smoke:clean-room`. The drift step catches a
  contributor who changed plugin deps without regenerating plugin/bun.lock.
- npm-publish.yml: add setup-bun and run `npm run smoke:clean-room` between
  build and `npm publish`, so a broken runtime closure cannot be published
  on a tag push (ci.yml does not run on tags). Secrets block untouched.

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10: doc recluster note + Phase 0 execution slice for #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plans: backlog recluster (2026-06-04) — cross-cluster execution order + plan-13 doc

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10: gen-plugin-lockfile degrades gracefully when bun is absent

The Windows build CI job has no bun on PATH; regenerating the lockfile there
threw and failed the build. The committed plugin/bun.lock is already the
deterministic closure, so skip regeneration (non-fatal) when bun is missing
and a lockfile exists; fail loud only when neither is available.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: rebuild plugin artifacts after merging main (v13.5.1) + plan-10 work

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: rebuild plugin artifacts after merging main v13.5.5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(deps): daily upgrade pass — agent SDK 0.3.172, better-auth 1.6.16, posthog-node 5.36.15, dompurify 3.4.9

- Bump @anthropic-ai/claude-agent-sdk 0.2.141 -> 0.3.172 (tsc + full test suite green)
- Remove deprecated @types/dompurify stub (dompurify ships its own types)
- Add overrides.tmp ^0.2.7 to clear GHSA-52f5-9888-hmc6 / GHSA-ph9p-34f9-6g65
  via np -> listr-input -> inquirer -> external-editor -> tmp chain
- npm audit: 0 vulnerabilities; npm outdated: clean
- package-lock.json is gitignored in this repo, so not committed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* plan: worker-restart single-source-of-truth — 7-phase fix for restart races

Phased plan from the adversarially-verified diagnosis (wf_f07f3541-b05):
kill the cache mirror, single verified restart initiator, self-replacing
restart endpoint, unified spawn gate with lockfile, PID-file demotion,
test data-dir isolation, soak verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(restart): delete sync-script cache-mirror and HTTP restart trigger

Phase 1 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
The installed-version cache mirror wrote version-N code into the
version-(N-1) cache dir, manufacturing permanent version disagreement;
the HTTP POST to /api/admin/restart raced the CLI restart that follows
it in build-and-sync. Both are deleted; the CLI worker:restart in the
marketplace copy is now the single restart initiator, and the sleep 1
between the two mechanisms is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(restart): restart proves itself or exits 1

Phase 2 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
worker-service restart now captures the old worker pid, waits for the
port with the same platform-scaled 15s budget as stop, spawns the
marketplace copy of worker-service.cjs when present, then polls
/api/health until the pid changes and the version matches this build's
baked __DEFAULT_PACKAGE_VERSION__ — success is printed to stdout,
deadline (platform-scaled 30s) exits 1 with the last observed health
payload and the spawned script path. The --daemon generic start-failure
path now exits 1 instead of masquerading as success; the three
duplicate-suppression exits remain 0.

New helper src/services/restart-verify.ts (worker-service.ts bootstraps
on import, so the helper lives in an import-safe module) with 8 tests
covering pid-flip success, stale pid, wrong version, unreachable
timeout, 503-degraded acceptance, and null-oldPid version-only
verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(restart): self-replacing worker — old worker spawns its successor

Phase 3 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
/api/admin/restart was kill-only: hooks that POSTed it then raced the
dying worker with their own lazy-spawn (the observed recycle ping-pong).
Now the dying worker spawns its successor itself — after a re-entrancy-
guarded, deadline-bounded (platform-scaled 10s) graceful shutdown, and
only once its port is confirmed free; stop and signal shutdowns stay
kill-only. The hook recycle path waits for that successor via
/api/health polling (HOOK_READINESS_TIMEOUT_MS budget) and lazy-spawns
only as a fallback, with a warn-only version re-check so a hook never
recycles more than once per invocation.

Shutdown sequence lives in import-safe src/services/worker-shutdown.ts
(worker-service.ts bootstraps on import); registerSignalHandlers no
longer pre-sets isShuttingDown — the supervisor's shutdownInitiated
guard owns signal dedupe, and pre-setting would no-op the new entry
guard. 13 new tests cover re-entrancy, deadline expiry/rejection,
handoff ordering, kill-only reasons, successor-wait vs lazy-spawn
fallback, and pre-graceful bookkeeping failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(restart): one spawn gate; CLI restart defers to the self-replacing worker

Phase 4 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
Three uncoordinated spawn paths (hook lazy-spawn, MCP worker-spawner,
CLI) with two different bun resolvers produced 3-launcher collisions
within a single second. Now a wx-flag lockfile (<DATA_DIR>/spawn.lock,
60s mtime staleness with re-stat-before-unlink, owner-checked release)
gates every external spawn: lock losers never fail — they skip the
spawn and wait for the winner's worker. resolveBunRuntime is deleted in
favor of ProcessManager's resolveWorkerRuntimePath (adds BUN_PATH,
~/.bun/bin, brew, which fallbacks), closing the kill-then-can't-respawn
path; mcp-server prefers the marketplace worker script so stale cache
dirs stop spawning stale workers.

Integration fix surfaced by live verification: the CLI restart raced
the Phase 3 self-replacement handoff (the successor re-binds the port
in ~200ms, so waitForPortFree always timed out and restart exited 1
while the restart had actually succeeded). The CLI now verifies the
worker's self-spawned successor directly, and only spawns — gate-
wrapped, after the port frees — as the fallback when no worker was
running, the shutdown POST was rejected, or no successor appeared. The
dying worker's handoff is intentionally ungated: it spawns only after
its own port closes, and hooks wait on it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(restart): demote the PID file — health and port are the liveness oracle

Phase 5 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
The dying worker's shutdown cascade deleted the PID file
unconditionally as its final act, clobbering the successor's
freshly-written file; status then required portInUse AND pidInfo, so a
healthy worker reported as "not running". Now every PID-file deletion
is owner-guarded: the supervisor cascade deletes only its own pid
(removeOwnedPidFile), and the CLI stop/restart-fallback, the restart
handoff, and the daemon start-failure cleanup go through
removePidFileIfOwner (owner-or-dead — a live successor's file always
survives; corrupt files are left for the next boot's validator).

status sources from GET /api/health alone (pid, version, uptime,
workerPath; 503-degraded counts as running and now surfaces its queue
detail), with port-in-use-but-unreachable and not-running fallbacks —
all exit 0 as before. The --daemon duplicate gate checks the port
first (ground truth) and the PID file second (advisory, for the
freed-port-but-undeleted-file window); duplicate suppression stays
exit 0. writePidFile/touchPidFile remain — the file is diagnostics,
and the worker stays its only writer.

Also fixes combined-run test pollution: spawn-gate and worker-utils
timeout tests now eagerly import paths.js before setting a temp
CLAUDE_MEM_DATA_DIR, so the import-time DATA_DIR const can't freeze on
a deleted temp dir for suites loaded later in the same bun process.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: no test ever touches the real ~/.claude-mem again

Phase 6 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
process-manager and graceful-shutdown tests wrote corrupt JSON and
sentinel PIDs (2147483647) into the real ~/.claude-mem/worker.pid and
drove the real supervisor.json cascade under a snapshot-restore that a
killed run would skip — that pollution contaminated production logs and
a prior diagnosis. Both files now set a temp CLAUDE_MEM_DATA_DIR at the
top of the file before dynamically importing the code under test (ESM
hoisting makes beforeEach too late), assert their paths landed outside
the real dir, and derive PID_FILE from the same frozen paths module the
code uses so test and code can never diverge under bun's shared module
cache. The snapshot-restore scaffolding is deleted; zero assertions
changed.

tests/preload.ts gains a tripwire: when CLAUDE_MEM_DATA_DIR is unset it
fills a per-run temp dir, so no test in any file can fall through to
the real data dir. Fallout made explicit: worker-spawn child processes
get an explicit temp dir; install-error-matrix restores rather than
deletes the env var; settings-defaults-manager pins the unset-env
default it was implicitly relying on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(settings): bootstrap notices go to stderr, never stdout

CI on PR #2894 caught the latent bug: on the first boot in a fresh data
dir, SettingsDefaultsManager printed '[SETTINGS] Created settings file
with defaults: ...' to stdout before the start command's JSON hook
payload, corrupting the machine-readable contract every fresh install's
first hook invocation relies on. The Phase 6 per-run temp data dir made
the cold-dir case deterministic in CI, exposing it. Both informational
notices (creation, nested-schema migration) now use console.warn —
stderr — matching the function's existing failure-path idiom; two
regression tests pin stdout silence on both paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(restart): address PR #2894 review — dedupe script resolver, skip futile port wait

Both inline copies of the marketplace-first script-candidate list in
worker-service.ts (restart fallback + successor handoff injection) now
call the exported resolveWorkerScriptPath() ?? __filename, so the
candidate list lives in one place. verifyRestartedWorker's failure
result gains lastPollSawHealth; when the self-replacement handoff
verification timed out while a live (but unverifiable) worker was still
serving on the port, the CLI fallback now skips its port-free wait —
the port cannot free while that worker lives, so the wait only burned
its full platform-scaled budget before the same final verification ran
anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(sdk): stale Claude CLI can no longer silently kill every observation

findClaudeExecutable validated candidates with --version only, so an
abandoned old install shadowing a current CLI in PATH (e.g. npm-global
2.0.42 next to the auto-updating native installer) passed validation,
then died at every Observer spawn: the SDK passes --permission-mode
dontAsk (hardened-options) which old CLIs reject with exit 1. Result:
healthy worker, zero observations, no visible error (#2782 family;
previously #1857/#2049/#1866/#2142 in the same class).

- Probe every candidate (which -a + known install paths) with
  `--permission-mode dontAsk --version`: one spawn proves both flag
  compatibility and version, no API call (~150ms)
- Prefer the newest capable version; PATH order only breaks ties
- Explicit CLAUDE_CODE_PATH still wins but fails loud with version and
  remedy when too old, instead of dying silently at spawn
- All-too-old throws an error naming each candidate, its version, and
  how to fix; resolution success logs at INFO with the chosen version
- Cache successful resolution 15 min (resolver runs per SDK query);
  never cache failure so a CLI update is picked up without restart
- SDK child keeps a 2KB stderr tail and includes it in the exit WARN,
  so "unknown option" deaths are diagnosable at default log level

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(audit): add ponytail audit master plan + SQLite removal companion

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slice-0): remove commit-hash fabrication verification

Removes the commit-verification system whose second-system effects (greedy hex
regex false-positives + compensating scaffolding) cost more than the edge case
it caught. Deletes src/sdk/commit-verification.ts + its 2 tests, the
verify-before-persist block + stripFabricatedHashesFromSummary in
ResponseProcessor, and the now-dead fabricated_count/fabrication_* telemetry
in buffer.ts/scrub.ts/npx-cli telemetry.ts.

Consequence: PostHog Fabrication Rate tile goes dark (no other producer).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slice-1): remove duplicate SQLite stack, test the shipping path

Two parallel SQLite systems lived in src/services/sqlite/. The worker only ever
used SessionStore (which runs its own inline migrations); the second stack —
ClaudeMemDatabase/MigrationRunner + a free-function CRUD API — was reachable only
from tests. This removes the dead duplicate and rewrites its tests against the
real SessionStore path.

Deleted: Database.ts, migrations/runner.ts, index.ts, SchemaRepair.ts, all 6 CRUD
barrels, transactions.ts, and the sessions/summaries/prompts/timeline/import
submodules (18 files). Trimmed the 5 survivor leaf functions to their single live
export. Removed dead SessionStore methods getSessionSummaryById (broken) and
storeObservationsAndMarkComplete (0 callers).

Replaced 9 old test files (coupled to the dead stack) with 7 focused
session-store-*.test.ts suites + a rewritten cleanup-v12_4_3 test, all exercising
the worker's real path.

Net ~-3,262 src lines. tsc clean; full suite 2253 pass / 0 fail; dead-reference
sweep zero. No production behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slice-6): drop dead storage methods, dedupe EnvManager, stdlib swaps

Dead-code deletes (all zero-caller verified): sqlite ProjectsRepository.upsert +
getByRootPath, ServerSessionsRepository.getByMemorySessionId,
MemoryItemsRepository.getByLegacyObservationId, pg AgentEventsRepository.createMany
+ TeamsRepository.getMember; shared getWorkerSocketPath, createBackupFilename, and
the deprecated ENV_FILE_PATH const.

Refactors (behavior-preserving): EnvManager's three open-coded 5-key credential
blocks collapse to one CREDENTIAL_KEYS loop (whitelist semantics kept, no
Object.assign); parseEnvFile line-parser → util.parseEnv; fetchWithTimeout's manual
setTimeout race → AbortSignal.timeout (caller timeout-message contract preserved).

Net ~-106 lines. tsc clean; full suite 2253 pass / 0 fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slice-4): sweep dead/redundant code from src/server

Deletes (zero-caller verified): jobs/outbox.ts (canonical path is
IngestEventsService/EndSessionService) + its test; the src/server/mcp/ surface
(live MCP server is src/servers/mcp-server.ts) + its test; the always-inert
ServerBetaProviderRegistry/EventBroadcaster boundaries (no Active variant exists)
+ their /v1/info payload keys; the unused isServerClassified guard; dead option
sessionDebounceWindowMs (route + IngestEventsService).

Dedupe: hoist the 5 byte-identical auth helpers shared by auth.ts/postgres-auth.ts
into request-auth-helpers.ts; collapse resolveSummaryQueue/resolveEventQueue into
one resolveQueue(lane).

Kept (plan premise didn't verify): the ServerV1Routes runtime? option — a live
smoke test sets and asserts it. Disabled* boundary subclasses kept (real Active
counterparts chosen at runtime).

Net ~-570 source lines. tsc clean; full suite 2242 pass / 0 fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slice-7): dead-code sweep + dedup across services + npx-cli

Deletes (zero-caller verified, tests removed in lockstep): ProcessManager
getChildProcesses + parseElapsedTime (ps-reaper leftovers), ModeManager
getObservationConcepts/validateType/getTypeLabel, AgentFormatter
renderAgentFileHeader/renderAgentColumnKey/renderAgentContextIndex (return-[] inert,
HeaderRenderer arms collapsed to Human-only), asyncHandler export,
PriorMessages.userMessage (always ''), and 3 dead barrels (services/context,
services/server, services/server/Middleware).

Dedup: shared toError() helper (src/utils/to-error.ts) across 8 in-scope sites;
asMs hoisted to telemetry/common; spawnPlugin() folds 4 spawnHidden blocks;
countObserverSessionRows extracts the CleanupV12_4_3 count trio; IS_WINDOWS imported
from paths.ts (was redeclared 3x); detectOsVersion inlined to os.release().

Kept (plan premise didn't verify): bun-resolver's spawnSync (npx-cli runs under
Node — Bun.which would ReferenceError); context-generator.ts (a build entrypoint +
test mock seam) — barrel still removed; Middleware.ts consumer Server.ts repointed
to the real middleware.

Net ~-279 lines. tsc clean; full suite 2232 pass / 0 fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slice-5): delete orphans + dead UI/util code across cli/supervisor/ui

Orphan deletes (zero-caller verified): src/bin/ (import-xml-observations,
cleanup-duplicates — not wired to any entrypoint), src/adapters/ (abandoned parallel
impl; real adapters are src/cli/adapters/), core/schemas/context-pack.ts +
core/schemas/index.ts barrel. Companion tests removed in lockstep.

Dead functions: 7 cursor-utils helpers (kept the 6 live registry/context writers),
logger correlationId/sessionId/timing.

YAGNI trims: gemini-cli metadata block (never read), useStats collapsed to
fire-and-forget (+ dead Stats/WorkerStats/DatabaseStats types), useTheme
resolvedTheme, useContextPreview.refresh from the public result; env-sanitizer
ENV_PROXY_VARS folded into ENV_EXACT_MATCHES.

Dedup: useGitHubStars + formatNumber inlined into their single consumer
GitHubStarsButton (kept lowercase-k display — matches GitHub, Intl would uppercase);
waitForExit extracted in process-registry and shared with shutdown's reapSession
(placed low to avoid a circular import).

Net ~-1085 lines (incl. test deletions). tsc + viewer typecheck clean;
full suite 2181 pass / 0 fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slice-2a): integrations/sync dead exports + scoped Chroma cleanup

Dead-export deletes (zero-caller verified): syncContextToAgentsMd,
updateWindsurfContextForProject, handleWindsurfCommand, detectClaudeCode (+ orphaned
exec/promisify imports), getScriptExtension/detectPlatform, getVersionCheckAbsolutePath
(+ companion plugin-distribution test edit). ChromaSyncState flush/resetCache/dirty
(bump/replace persist eagerly, so dirty was always false).

Scoped to the Chroma write path only (SQLite/search/telemetry copies stay live):
dropped the created_at ISO field (kept created_at_epoch, the field actually read back)
and the discovery_tokens param/field from ChromaSync's local Stored interfaces + the
3 live call sites.

Stdlib/shrink: parseSemver/compareSemver → localeCompare numeric (ordering verified);
OpenClaw roots hoisted to one const; bootstrapWatermarks max-loop → Math.max;
Goose YAML builders collapsed to one withHeader flag.

Defers the 5-installer consolidation to a separate pass. Net ~-187 lines.
tsc clean; full suite 2181 pass / 0 fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slice-2b): delete dead fetchAndInjectOpenCodeContext

Orphaned when slice-2a removed syncContextToAgentsMd (its only caller chain).
Confirmed zero callers. Kept fetchRealContextFromWorker (live at :323).

Per feasibility analysis, the plan's 5-installer table-driven consolidation
(headline -2400) was descoped: the 5 installers write 5 different config schemas
and 4 different context formats with thin test coverage, so a 5->1 collapse would
relocate divergence into callbacks and risk untested IDE-config write paths for a
line-count-only win. Plan gate ('only if behavior genuinely identical') not met.

tsc clean; full suite 2181 pass / 0 fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(slice-3): worker dead-code sweep + provider/search consolidation

Search pipeline: delete the unreachable half built speculatively (TimelineBuilder,
ResultFormatter instance methods [kept the one live static], search/filters/,
SearchStrategy.canHandle/name + BaseSearchStrategy, HybridSearchStrategy no-op
search(), dead SearchOrchestrator wrappers, ChromaQueryResult) + companion tests.
Dedup the 5 genuinely-identical chroma->recency->hydrate->FTS paths into one
hybridSemanticHydrate helper and the 3 timeline renderers into renderTimeline()
(search()/decisions()/changes()/howItWorks() left as-is — different shape).

Providers: extract OpenAICompatibleProvider base from the Gemini/OpenRouter twins,
preserving every per-provider divergence via flags/abstracts (truncation guard,
token estimation, empty-response handling, endpointClass, the Gemini-only RPM
throttle). Dedup parseRetryAfterMs into retry.ts.

http/agents/session: delete the IngestEventBus machinery + ingestPrompt, the
read-only POST /api/processing dup, FallbackErrorHandler.shouldFallbackToClaude,
RateLimitStore.getAll/clear, SessionCompletionHandler.completeByDbId,
SessionCleanupHelper, 3 dead agent types, and no-op empty-passthrough validateBody;
collapse the 6 CorpusRoutes 404 blocks into one helper.

Kept (false-dead, caller verified): SSEEventPayload (live via WorkerRef), RateLimitStore.get
(live via shouldAbortForQuota). Both adversarial reviews confirmed behavior preserved.

Net ~-2,640 lines. tsc clean; full suite 2080 pass / 0 fail; dead-ref sweep zero.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* build(plugin): regenerate distribution artifacts after ponytail audit

Rebuilds the worker/mcp/server-beta/transcript-watcher/context-generator bundles
and the viewer UI from the post-audit source (Slices 0-7).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(env): require Node >=20.12.0 to match util.parseEnv usage

EnvManager adopted stdlib util.parseEnv (added in Node 20.12.0) during
the ponytail audit, but engines.node still advertised >=20.0.0. On Node
20.0-20.11 parseEnv is undefined: loadClaudeMemEnv() silently returns {}
(credentials never load) and saveClaudeMemEnv() throws. Declare the real
floor in both the npm package and the generated plugin manifest (via its
generator in build-hooks.js so rebuilds stay consistent).

Greptile PR #3021 P1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 13:12:41 -07:00
Alex Newman
b90ec02177 chore: bump version to 13.7.0 2026-06-20 00:04:30 -07:00
Alex Newman
580621c936 PostHog telemetry overhaul: per-session rollups, unified instrumentation, redacted error tracking (#3010)
* feat(telemetry): add unified instrument() layer fanning out to logger + telemetry

Introduces src/services/telemetry/instrument.ts as the single
instrumentation entry point: always writes the local log line, then
(consent-gated, swallow-all) routes scrubbed props to the telemetry
sink. Logger stays telemetry-free (no cycle). Migrates 3 exemplar
call sites (SessionRoutes, BaseRouteHandler, ResponseProcessor) from
duplicated log+capture pairs to single instrument() calls.

Phase 1 of the PostHog telemetry overhaul (plans/2026-06-19-posthog-telemetry-overhaul.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(telemetry): emit per-session session_compressed rollups at session end

Replaces the 5-minute time-window session rollup with a per-session
accumulator keyed by sessionDbId. Flushes one observer_turn_rollup per
session on session_end (deleteSession/removeSessionImmediate),
worker_shutdown (drained in shutdownTelemetry before the PostHog client
closes), and a periodic safety_flush for over-cap sessions (window_seq
bump + re-arm). Adds rollup_reason/window_seq to the scrub whitelist.
context_injected stays a time-window rollup (hook-level, no sessionDbId).

Fixes a shutdown-drain ordering bug where worker_shutdown rollups were
dropped because isShutdown latched before the drain; adds a
shutdownTelemetry() integration test covering it.

Phase 2 of the PostHog telemetry overhaul.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(telemetry): capture redacted error data via PostHog Error Tracking

Adds $exception capture with an allow-then-redact error scrubber
(error-scrub.ts): home dir, absolute paths, DB connection-string creds,
URL userinfo, emails, API tokens (sk-/phc-/ghp-/AWS AKIA/JWT), hex and
IPv4 are stripped; message capped 500 / stack ~2KB. captureException is
consent-gated, profile-less ($process_person_profile:false), and
fingerprint rate-limited (1/min/fingerprint, bounded LRU map). Manual
errors route via a no-cycle logger error sink (logger.setErrorSink).

Worker enables enableExceptionAutocapture, with errorBeforeSend fully
redacting autocaptured $exception_list payloads (deletes source
context_line/pre_context/post_context, redacts value + filename) so the
SDK's on-disk source-context capture can never exfiltrate code. A
sentinel marks manual captures so the limiter runs exactly once and
occurrence_count ships intact. Redaction inputs are hard-capped at 8KB
with bounded regexes to prevent ReDoS on the worker.

New kill-switch CLAUDE_MEM_TELEMETRY_ERRORS=0 disables error capture
independently of analytics.

Phase 3 of the PostHog telemetry overhaul.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(telemetry): canonicalize rollup naming and document the new model

Fixes a stale session_compressed_rollup comment to the canonical live
event name (observer_turn_rollup). Rewrites docs/public/telemetry.mdx to
reflect the overhaul: per-session observer_turn_rollup with
rollup_reason/window_seq, the new redacted $exception error tracking
(what is kept vs redacted, rate-limiting, consent gate, one-way-door
note), the CLAUDE_MEM_TELEMETRY_ERRORS opt-out, the unified instrument()
logging model, and a note that PostHog session replay is N/A for a
headless Node worker. Reconciles "What is NEVER collected" honestly now
that redacted error messages are collected. No raw session_compressed or
context_injected events remain; only rollups are emitted.

Phase 5 of the PostHog telemetry overhaul.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* build(telemetry): regenerate plugin artifacts with telemetry overhaul

Rebuilds the tracked plugin/ bundle so the published plugin emits the
new per-session observer_turn_rollup (rollup_reason/window_seq), the
redacted $exception error tracking, and the unified instrument() layer.
The committed artifacts were stale (last built before Phases 1-5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 00:02:36 -07:00
Alex Newman
0e5f4ce7e3 chore: bump version to 13.6.2 2026-06-17 13:18:32 -07:00
Alex Newman
3b1014a305 chore: bump version to 13.6.1 2026-06-15 00:05:17 -07:00
Alex Newman
e9ca970214 chore: bump version to 13.6.0 2026-06-12 18:13:05 -07:00
Alex Newman
82361e7826 chore: bump version to 13.5.7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:04:07 -07:00
Alex Newman
608611b627 fix(sdk): stale Claude CLI can no longer silently kill every observation (#2911)
* fix(sdk): stale Claude CLI can no longer silently kill every observation

findClaudeExecutable validated candidates with --version only, so an
abandoned old install shadowing a current CLI in PATH (e.g. npm-global
2.0.42 next to the auto-updating native installer) passed validation,
then died at every Observer spawn: the SDK passes --permission-mode
dontAsk (hardened-options) which old CLIs reject with exit 1. Result:
healthy worker, zero observations, no visible error (#2782 family;
previously #1857/#2049/#1866/#2142 in the same class).

- Probe every candidate (which -a + known install paths) with
  `--permission-mode dontAsk --version`: one spawn proves both flag
  compatibility and version, no API call (~150ms)
- Prefer the newest capable version; PATH order only breaks ties
- Explicit CLAUDE_CODE_PATH still wins but fails loud with version and
  remedy when too old, instead of dying silently at spawn
- All-too-old throws an error naming each candidate, its version, and
  how to fix; resolution success logs at INFO with the chosen version
- Cache successful resolution 15 min (resolver runs per SDK query);
  never cache failure so a CLI update is picked up without restart
- SDK child keeps a 2KB stderr tail and includes it in the exit WARN,
  so "unknown option" deaths are diagnosable at default log level

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* build: regenerate plugin bundles with stale-CLI resolver fix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(supervisor): log SDK exit stderr tail on 'close' so it is never truncated

'exit' can fire before piped stderr has drained, so the WARN log could
miss the tail end of the CLI's dying words — the exact text the tail
exists to capture. 'close' waits for all stdio to finish. Registry
unregistration stays on 'exit' since it doesn't read the streams.

Addresses greptile review on PR #2911.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(resolver): cover the broken-candidate path; correct probe-count comment

The broken branch (fails both the capability probe and plain --version)
had no coverage: desktop-app skip warnings, the generic failed-probe
warning, not-found fall-through, and both CLAUDE_CODE_PATH failure
messages are now asserted. Also rewrites the probeCandidate doc comment,
which claimed a single spawn — accurate only for capable CLIs; failing
candidates intentionally get a second plain --version spawn to split
'too old' from 'broken' without pattern-matching stderr.

Addresses greptile review on PR #2911.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:52:17 -07:00
Alex Newman
47277b0c34 chore: bump version to 13.5.6 2026-06-10 20:06:33 -07:00
Alex Newman
c0b96288a7 fix(restart): worker-restart single source of truth — self-replacing worker, spawn gate, verified restarts (#2894)
* plan-10 Phase 1: ship deterministic plugin runtime dependency closure

Approach A — commit & ship plugin/bun.lock so the plugin's runtime
node_modules install is deterministic, fixing the recurring
`Cannot find module 'zod/v3'` (#2730).

- align generated plugin zod range to root (^4.4.3) in build-hooks.js
- new scripts/gen-plugin-lockfile.cjs generates plugin/bun.lock as a
  build artifact after build-hooks.js writes plugin/package.json
- track & ship plugin/bun.lock (.gitignore negation, .npmignore, files allowlist)
- install with `bun install --frozen-lockfile --ignore-scripts` at runtime

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10 Phase 2: fail loud at install time on a broken dependency closure

Strengthen verifyCriticalModules to assert each dependency is actually
importable via require.resolve (not merely a directory), and assert the
worker-required zod subpaths resolve: zod/v3, zod/v4, zod/v4-mini.
A partial/stale install now fails `npx claude-mem install` immediately
instead of surfacing later as a Stop-hook `Cannot find module 'zod/v3'`.

Bin-only packages (e.g. tree-sitter-cli, which has no bare-name entry
point) fall back to resolving <dep>/package.json so a healthy install
isn't falsely rejected.

Adds tests/cli/verify-critical-modules.test.ts covering a missing zod/v3
subpath (throws), a complete zod (passes), and a bin-only dep (passes).

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10 Phase 3: clean-room install + import smoke test (#2730 backstop)

Add scripts/smoke-clean-room.cjs and a `smoke:clean-room` npm script.
Against fresh temp dirs (never the repo's node_modules) it:
- copies plugin/, runs `bun install --frozen-lockfile --ignore-scripts`,
  asserts zod, zod/v3, zod/v4, zod/v4-mini resolve, and boots the bundled
  worker asserting no `Cannot find module` — the direct #2730 regression guard;
- `npm pack`s, installs the tarball into a second temp dir, and load-tests
  the published bin entrypoint, warning loudly on any declared main/exports
  target missing from the tarball (latent #2537 gap).

Exits non-zero naming the missing module on any failure; cleans up all
temp dirs and the tarball in a finally.

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10 Phase 4: gate CI and publish on the clean-room dependency closure

- ci.yml: new `clean-room-deps` job (between build and the docker e2e job)
  runs a frozen-lockfile drift check on the committed plugin lockfile, then
  `npm run build` + `npm run smoke:clean-room`. The drift step catches a
  contributor who changed plugin deps without regenerating plugin/bun.lock.
- npm-publish.yml: add setup-bun and run `npm run smoke:clean-room` between
  build and `npm publish`, so a broken runtime closure cannot be published
  on a tag push (ci.yml does not run on tags). Secrets block untouched.

Refs #2783, #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10: doc recluster note + Phase 0 execution slice for #2730

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plans: backlog recluster (2026-06-04) — cross-cluster execution order + plan-13 doc

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* plan-10: gen-plugin-lockfile degrades gracefully when bun is absent

The Windows build CI job has no bun on PATH; regenerating the lockfile there
threw and failed the build. The committed plugin/bun.lock is already the
deterministic closure, so skip regeneration (non-fatal) when bun is missing
and a lockfile exists; fail loud only when neither is available.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: rebuild plugin artifacts after merging main (v13.5.1) + plan-10 work

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: rebuild plugin artifacts after merging main v13.5.5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(deps): daily upgrade pass — agent SDK 0.3.172, better-auth 1.6.16, posthog-node 5.36.15, dompurify 3.4.9

- Bump @anthropic-ai/claude-agent-sdk 0.2.141 -> 0.3.172 (tsc + full test suite green)
- Remove deprecated @types/dompurify stub (dompurify ships its own types)
- Add overrides.tmp ^0.2.7 to clear GHSA-52f5-9888-hmc6 / GHSA-ph9p-34f9-6g65
  via np -> listr-input -> inquirer -> external-editor -> tmp chain
- npm audit: 0 vulnerabilities; npm outdated: clean
- package-lock.json is gitignored in this repo, so not committed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* plan: worker-restart single-source-of-truth — 7-phase fix for restart races

Phased plan from the adversarially-verified diagnosis (wf_f07f3541-b05):
kill the cache mirror, single verified restart initiator, self-replacing
restart endpoint, unified spawn gate with lockfile, PID-file demotion,
test data-dir isolation, soak verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(restart): delete sync-script cache-mirror and HTTP restart trigger

Phase 1 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
The installed-version cache mirror wrote version-N code into the
version-(N-1) cache dir, manufacturing permanent version disagreement;
the HTTP POST to /api/admin/restart raced the CLI restart that follows
it in build-and-sync. Both are deleted; the CLI worker:restart in the
marketplace copy is now the single restart initiator, and the sleep 1
between the two mechanisms is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(restart): restart proves itself or exits 1

Phase 2 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
worker-service restart now captures the old worker pid, waits for the
port with the same platform-scaled 15s budget as stop, spawns the
marketplace copy of worker-service.cjs when present, then polls
/api/health until the pid changes and the version matches this build's
baked __DEFAULT_PACKAGE_VERSION__ — success is printed to stdout,
deadline (platform-scaled 30s) exits 1 with the last observed health
payload and the spawned script path. The --daemon generic start-failure
path now exits 1 instead of masquerading as success; the three
duplicate-suppression exits remain 0.

New helper src/services/restart-verify.ts (worker-service.ts bootstraps
on import, so the helper lives in an import-safe module) with 8 tests
covering pid-flip success, stale pid, wrong version, unreachable
timeout, 503-degraded acceptance, and null-oldPid version-only
verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(restart): self-replacing worker — old worker spawns its successor

Phase 3 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
/api/admin/restart was kill-only: hooks that POSTed it then raced the
dying worker with their own lazy-spawn (the observed recycle ping-pong).
Now the dying worker spawns its successor itself — after a re-entrancy-
guarded, deadline-bounded (platform-scaled 10s) graceful shutdown, and
only once its port is confirmed free; stop and signal shutdowns stay
kill-only. The hook recycle path waits for that successor via
/api/health polling (HOOK_READINESS_TIMEOUT_MS budget) and lazy-spawns
only as a fallback, with a warn-only version re-check so a hook never
recycles more than once per invocation.

Shutdown sequence lives in import-safe src/services/worker-shutdown.ts
(worker-service.ts bootstraps on import); registerSignalHandlers no
longer pre-sets isShuttingDown — the supervisor's shutdownInitiated
guard owns signal dedupe, and pre-setting would no-op the new entry
guard. 13 new tests cover re-entrancy, deadline expiry/rejection,
handoff ordering, kill-only reasons, successor-wait vs lazy-spawn
fallback, and pre-graceful bookkeeping failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(restart): one spawn gate; CLI restart defers to the self-replacing worker

Phase 4 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
Three uncoordinated spawn paths (hook lazy-spawn, MCP worker-spawner,
CLI) with two different bun resolvers produced 3-launcher collisions
within a single second. Now a wx-flag lockfile (<DATA_DIR>/spawn.lock,
60s mtime staleness with re-stat-before-unlink, owner-checked release)
gates every external spawn: lock losers never fail — they skip the
spawn and wait for the winner's worker. resolveBunRuntime is deleted in
favor of ProcessManager's resolveWorkerRuntimePath (adds BUN_PATH,
~/.bun/bin, brew, which fallbacks), closing the kill-then-can't-respawn
path; mcp-server prefers the marketplace worker script so stale cache
dirs stop spawning stale workers.

Integration fix surfaced by live verification: the CLI restart raced
the Phase 3 self-replacement handoff (the successor re-binds the port
in ~200ms, so waitForPortFree always timed out and restart exited 1
while the restart had actually succeeded). The CLI now verifies the
worker's self-spawned successor directly, and only spawns — gate-
wrapped, after the port frees — as the fallback when no worker was
running, the shutdown POST was rejected, or no successor appeared. The
dying worker's handoff is intentionally ungated: it spawns only after
its own port closes, and hooks wait on it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(restart): demote the PID file — health and port are the liveness oracle

Phase 5 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
The dying worker's shutdown cascade deleted the PID file
unconditionally as its final act, clobbering the successor's
freshly-written file; status then required portInUse AND pidInfo, so a
healthy worker reported as "not running". Now every PID-file deletion
is owner-guarded: the supervisor cascade deletes only its own pid
(removeOwnedPidFile), and the CLI stop/restart-fallback, the restart
handoff, and the daemon start-failure cleanup go through
removePidFileIfOwner (owner-or-dead — a live successor's file always
survives; corrupt files are left for the next boot's validator).

status sources from GET /api/health alone (pid, version, uptime,
workerPath; 503-degraded counts as running and now surfaces its queue
detail), with port-in-use-but-unreachable and not-running fallbacks —
all exit 0 as before. The --daemon duplicate gate checks the port
first (ground truth) and the PID file second (advisory, for the
freed-port-but-undeleted-file window); duplicate suppression stays
exit 0. writePidFile/touchPidFile remain — the file is diagnostics,
and the worker stays its only writer.

Also fixes combined-run test pollution: spawn-gate and worker-utils
timeout tests now eagerly import paths.js before setting a temp
CLAUDE_MEM_DATA_DIR, so the import-time DATA_DIR const can't freeze on
a deleted temp dir for suites loaded later in the same bun process.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: no test ever touches the real ~/.claude-mem again

Phase 6 of plans/2026-06-10-worker-restart-single-source-of-truth.md.
process-manager and graceful-shutdown tests wrote corrupt JSON and
sentinel PIDs (2147483647) into the real ~/.claude-mem/worker.pid and
drove the real supervisor.json cascade under a snapshot-restore that a
killed run would skip — that pollution contaminated production logs and
a prior diagnosis. Both files now set a temp CLAUDE_MEM_DATA_DIR at the
top of the file before dynamically importing the code under test (ESM
hoisting makes beforeEach too late), assert their paths landed outside
the real dir, and derive PID_FILE from the same frozen paths module the
code uses so test and code can never diverge under bun's shared module
cache. The snapshot-restore scaffolding is deleted; zero assertions
changed.

tests/preload.ts gains a tripwire: when CLAUDE_MEM_DATA_DIR is unset it
fills a per-run temp dir, so no test in any file can fall through to
the real data dir. Fallout made explicit: worker-spawn child processes
get an explicit temp dir; install-error-matrix restores rather than
deletes the env var; settings-defaults-manager pins the unset-env
default it was implicitly relying on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(settings): bootstrap notices go to stderr, never stdout

CI on PR #2894 caught the latent bug: on the first boot in a fresh data
dir, SettingsDefaultsManager printed '[SETTINGS] Created settings file
with defaults: ...' to stdout before the start command's JSON hook
payload, corrupting the machine-readable contract every fresh install's
first hook invocation relies on. The Phase 6 per-run temp data dir made
the cold-dir case deterministic in CI, exposing it. Both informational
notices (creation, nested-schema migration) now use console.warn —
stderr — matching the function's existing failure-path idiom; two
regression tests pin stdout silence on both paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(restart): address PR #2894 review — dedupe script resolver, skip futile port wait

Both inline copies of the marketplace-first script-candidate list in
worker-service.ts (restart fallback + successor handoff injection) now
call the exported resolveWorkerScriptPath() ?? __filename, so the
candidate list lives in one place. verifyRestartedWorker's failure
result gains lastPollSawHealth; when the self-replacement handoff
verification timed out while a live (but unverifiable) worker was still
serving on the port, the CLI fallback now skips its port-free wait —
the port cannot free while that worker lives, so the wait only burned
its full platform-scaled budget before the same final verification ran
anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 19:59:34 -07:00