Summary
When CLAUDE_MEM_RUNTIME=server-beta, new Claude Code sessions inject stale or empty context. The SessionStart context-injection hook is not runtime-aware: it unconditionally targets the local worker's GET /api/context/inject route (backed by local SQLite) and never queries server-beta's PostgreSQL.
Impact
- In
server-beta mode the worker is typically not running, so the hook hits the worker-unreachable fallback and injects an empty context string → sessions start with no memory.
- If a stale worker is running, it injects from stale local SQLite instead of server-beta's Postgres → stale context.
- Bonus symptom: each miss calls
recordWorkerUnreachable(), which after CLAUDE_MEM_HOOK_FAIL_LOUD_THRESHOLD (3) consecutive misses emits a blocking "worker unreachable" error — in a configuration where the worker is intentionally absent.
Root cause
src/cli/handlers/context.ts (the context hook handler, dispatched from src/cli/handlers/index.ts) contains no runtime selection. Every sibling handler is runtime-aware (session-init.ts:14, observation.ts, summarize.ts import resolveRuntimeContext and branch on runtime.runtime === 'server-beta'), but the context handler was never given that branch.
Code path:
plugin/hooks/hooks.json SessionStart → worker-service.cjs hook claude-code context
src/cli/handlers/index.ts:23 maps 'context' → contextHandler
src/cli/handlers/context.ts:29 hardcodes /api/context/inject?projects=…
src/cli/handlers/context.ts:37 executeWithWorkerFallback<string>(apiPath, 'GET')
src/cli/handlers/context.ts:38-40 returns empty additionalContext on worker fallback
- worker route
src/services/worker/http/routes/SearchRoutes.ts:153 reads local SQLite
The gap is already acknowledged in-tree:
src/cli/handlers/session-init.ts:73-76: "Server-beta does not currently support the same context-injection protocol as the worker. Skip semantic injection in server-beta mode until the server-beta context endpoint exists."
docs/server-beta-parity-map.md:68: GET /api/context/inject → (none yet) → unsupported
Server-beta status
POST /v1/context exists (src/server/routes/v1/ServerV1PostgresRoutes.ts:895, Postgres-backed, returns {observations, context}), with client method contextObservations({projectId, query, limit}) (src/services/hooks/server-beta-client.ts:255).
- However
/v1/context is query-driven FTS — query: z.string().min(1) is required. The worker inject is query-less (recent observations for the project). So server-beta currently has no equivalent of the unconditional SessionStart "recent timeline" inject.
Proposed fix
- Add a query-less recent-context server route (e.g.
GET /v1/context/recent?projectId=&limit=) that returns the same {observations, context} shape from PostgresObservationRepository, mirroring the worker's recent-observations semantics.
- Add a corresponding client method on
ServerBetaClient.
- Make
contextHandler runtime-aware (mirror session-init.ts): on server-beta, call the new endpoint; on worker, keep existing behavior. Preserve the fallback-to-worker path on eligible server-beta client errors.
- Update
docs/server-beta-parity-map.md to reflect the new endpoint.
This fills the exact parity-map gap and preserves injection semantics (recent timeline, not FTS relevance).
Environment
CLAUDE_MEM_RUNTIME=server-beta
- Affects every new/cleared/compacted session's SessionStart context injection.
Summary
When
CLAUDE_MEM_RUNTIME=server-beta, new Claude Code sessions inject stale or empty context. The SessionStart context-injection hook is not runtime-aware: it unconditionally targets the local worker'sGET /api/context/injectroute (backed by local SQLite) and never queries server-beta's PostgreSQL.Impact
server-betamode the worker is typically not running, so the hook hits the worker-unreachable fallback and injects an empty context string → sessions start with no memory.recordWorkerUnreachable(), which afterCLAUDE_MEM_HOOK_FAIL_LOUD_THRESHOLD(3) consecutive misses emits a blocking "worker unreachable" error — in a configuration where the worker is intentionally absent.Root cause
src/cli/handlers/context.ts(thecontexthook handler, dispatched fromsrc/cli/handlers/index.ts) contains no runtime selection. Every sibling handler is runtime-aware (session-init.ts:14,observation.ts,summarize.tsimportresolveRuntimeContextand branch onruntime.runtime === 'server-beta'), but the context handler was never given that branch.Code path:
plugin/hooks/hooks.jsonSessionStart →worker-service.cjs hook claude-code contextsrc/cli/handlers/index.ts:23maps'context' → contextHandlersrc/cli/handlers/context.ts:29hardcodes/api/context/inject?projects=…src/cli/handlers/context.ts:37executeWithWorkerFallback<string>(apiPath, 'GET')src/cli/handlers/context.ts:38-40returns emptyadditionalContexton worker fallbacksrc/services/worker/http/routes/SearchRoutes.ts:153reads local SQLiteThe gap is already acknowledged in-tree:
src/cli/handlers/session-init.ts:73-76: "Server-beta does not currently support the same context-injection protocol as the worker. Skip semantic injection in server-beta mode until the server-beta context endpoint exists."docs/server-beta-parity-map.md:68:GET /api/context/inject→ (none yet) → unsupportedServer-beta status
POST /v1/contextexists (src/server/routes/v1/ServerV1PostgresRoutes.ts:895, Postgres-backed, returns{observations, context}), with client methodcontextObservations({projectId, query, limit})(src/services/hooks/server-beta-client.ts:255)./v1/contextis query-driven FTS —query: z.string().min(1)is required. The worker inject is query-less (recent observations for the project). So server-beta currently has no equivalent of the unconditional SessionStart "recent timeline" inject.Proposed fix
GET /v1/context/recent?projectId=&limit=) that returns the same{observations, context}shape fromPostgresObservationRepository, mirroring the worker's recent-observations semantics.ServerBetaClient.contextHandlerruntime-aware (mirrorsession-init.ts): onserver-beta, call the new endpoint; on worker, keep existing behavior. Preserve the fallback-to-worker path on eligible server-beta client errors.docs/server-beta-parity-map.mdto reflect the new endpoint.This fills the exact parity-map gap and preserves injection semantics (recent timeline, not FTS relevance).
Environment
CLAUDE_MEM_RUNTIME=server-beta