Skip to content

server-beta: SessionStart context injection is not runtime-aware (injects stale/empty context) #2991

Description

@arclabch

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

  1. 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.
  2. Add a corresponding client method on ServerBetaClient.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions