refactor: share hook spool, bounded JSON parser, and memory kinds - #1473
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Pull request overview
This PR refactors shared primitives that multiple integrations depend on (CLI hook-ingest durability, viewer-server bounded JSON parsing, and a canonical memory-kind catalog) so follow-up “pi” integration layers can reuse the same building blocks without duplicating logic.
Changes:
- Extracts a shared
createHookIngestSpooldurability layer (lock + spool + recovery + drain) and wires Claude/Codex wrappers to it. - Promotes the bounded JSON object body parser into
packages/viewer-server/src/helpers.tsand reuses it from the raw-events routes. - Introduces a core
memory-kindscatalog + validation, and re-exports the MCP catalog from core to prevent drift.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/viewer-server/src/routes/raw-events.ts | Switches to using the shared parseJsonObjectBody helper for bounded JSON parsing. |
| packages/viewer-server/src/helpers.ts | Exports parseJsonObjectBody so routes can share a single bounded JSON parser implementation. |
| packages/viewer-server/src/helpers.test.ts | Adds unit tests covering parseJsonObjectBody size bounds and shape validation. |
| packages/mcp-server/src/memory-kinds.ts | Re-exports the MCP memory-kind descriptions from @codemem/core to prevent catalog drift. |
| packages/core/src/store.ts | Replaces local memory-kind validation with validateMemoryKind imported from the new core catalog. |
| packages/core/src/memory-kinds.ts | Adds canonical memory-kind catalog + allowed set + validateMemoryKind. |
| packages/core/src/memory-kinds.test.ts | Adds tests to lock down the catalog shape and validation behavior. |
| packages/core/src/index.ts | Re-exports memory-kinds from core’s public entrypoint. |
| packages/core/src/filters.ts | Hardens the “kind” filter against non-string JSON inputs to avoid malformed bound parameters. |
| packages/cli/src/commands/hook-ingest-spool.ts | Adds the shared hook-ingest lock/spool/recovery/drain implementation used by multiple CLI commands. |
| packages/cli/src/commands/codex-hook-ingest-spool.ts | Replaces duplicated codex lock/spool code with configuration over the shared spool implementation. |
| packages/cli/src/commands/claude-hook-ingest.ts | Changes HTTP-success flow to drain any backlog before running boundary flush. |
| packages/cli/src/commands/claude-hook-ingest.test.ts | Adds a regression test asserting backlog drain occurs before boundary flush on HTTP-success. |
| packages/cli/src/commands/claude-hook-ingest-spool.ts | Replaces duplicated claude lock/spool code with configuration over the shared spool implementation (retaining flush predicate). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
kunickiaj
left a comment
There was a problem hiding this comment.
Thanks for splitting this foundation out of #1430—the shared spool factory and bounded JSON helper are much easier to review in this form, and the targeted tests pass locally (187 tests plus the CLI TypeScript build).
I found two items I think we should address before merging:
- This branch currently conflicts with
main. Recent changes addedrawEventTarget/targetMismatchhandling in the same Claude and Codex ingest paths touched here. Could you restack and preserve those safeguards during conflict resolution? - The new memory-kind catalog does not yet drive the MCP validation schema, so kind enforcement can still drift.
I also left a couple of focused test suggestions for the new abstraction. Once the branch is restacked and those catalog changes are addressed, I would be happy to take another pass.
| * the kinds never drift. | ||
| */ | ||
|
|
||
| export const MEMORY_KIND_DESCRIPTIONS: Record<string, string> = { |
There was a problem hiding this comment.
Nice direction—having the descriptions and store validation share one catalog removes an existing source of drift.
Could we also derive the actual MCP validator from this catalog? packages/mcp-server/src/schemas.ts still has a separate hardcoded z.enum(...), while ingest-pipeline.ts and ingest-xml-parser.ts retain similar lists.
Preserving literal keys here with something like as const satisfies Record<string, string> would let REMEMBER_MEMORY_KINDS drive the MCP schema and make the “canonical catalog” invariant enforceable rather than documentary.
There was a problem hiding this comment.
memoryKindSchema is now z.enum(REMEMBER_MEMORY_KINDS). Ingest-pipeline and SUPPORTED_OBSERVATION_KINDS use the same tuple.
|
|
||
| export type { SpoolDrainResult } from "./hook-ingest-spool.js"; | ||
|
|
||
| const spool = createHookIngestSpool({ |
There was a problem hiding this comment.
Could we add a small focused test for the Codex factory configuration? The extraction moves several important behaviors into this config object—environment variable names, TTL, spool directory, retry count, and error identity—but the detailed spool tests currently exercise only the Claude instance.
At minimum, it would be useful to assert the Codex TTL/env overrides and that Claude and Codex resolve to separate spool directories. That would protect against a future configuration typo silently mixing the queues.
There was a problem hiding this comment.
Added codex-hook-ingest-spool.test.ts for the 120s TTL, env override, separate spool dirs, and CodexHookLockBusyError.
| // Drain any spooled backlog before the boundary flush so the | ||
| // flush pass sees every queued payload of the session, not just | ||
| // this event. | ||
| await drainBacklogIfPresent(); |
There was a problem hiding this comment.
Draining the backlog before the boundary flush makes sense and fixes the ordering issue described in the PR.
Could the regression test record both the queued HTTP handling and boundaryFlush in one sequence, then assert that the drain completes first? The current test confirms that the backlog drains, but its payload is a Stop event with flushing disabled by default, so it does not exercise the ordering this change is intended to guarantee.
There was a problem hiding this comment.
The SessionEnd case already records http:queued before flush:fresh. Left that test in place.
| } | ||
| if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) { | ||
| // Parseable but wrong shape — same problem, same fix. | ||
| quarantineSpoolEntry(dir, name, "wrong-shape"); |
There was a problem hiding this comment.
This shared implementation now increments failed for wrong-shape Claude spool entries, whereas the previous Claude implementation quarantined them without incrementing the count. The new behavior looks more consistent, but could we assert the returned { processed, failed } result in the existing wrong-shape test so the intentional change is captured?
There was a problem hiding this comment.
The existing non-object test now asserts { processed: 0, failed: 2 }.
cfc0654 to
d21ae92
Compare
|
Restacked onto current |
d21ae92 to
f8c95d1
Compare
There was a problem hiding this comment.
Re-reviewed at f8c95d1. All prior requested changes are addressed: the branch is cleanly restacked with viewer-target safeguards preserved, MCP and observer enforcement now derive from the core memory-kind catalog, Codex spool configuration/isolation has focused coverage, the boundary-flush ordering is asserted, and wrong-shape drain accounting is covered.
Local validation passed: 202 targeted tests, workspace TypeScript build, and Biome lint. CI is also green. No merge blockers remain.
Approving this revision. Please hold the merge until after the 0.43 release so it lands in the intended release sequence.
Extract the claude/codex lock-and-spool copies into one parameterized helper so a later pi client does not add a third concurrency path. Export the viewer bounded JSON parser and a single core kind catalog (seven remember kinds plus session_summary) so HTTP and MCP cannot drift. Drain the claude HTTP-success backlog before boundary flush.
Layer-1 review: the typeof guard in buildFilterClauses had no regression test.
Keep remember-kind enforcement on one const catalog so the MCP zod enum and observer ingest lists cannot drift independently.
Cover Codex TTL/env/dir isolation plus the shared drain result for quarantined non-object spool entries.
f8c95d1 to
55531d7
Compare
|
@kunickiaj 0.43 is out. This is still approved from Aug 25. Restacked onto current |
Merge activity
|
|
@kunickiaj Graphite couldn’t merge this: fast-forward merge isn’t supported for fork PRs. GitHub shows it mergeable with green CI. Could you merge it from the GitHub UI (Merge or Squash) instead of Graphite? |
First stacked PR for the pi integration review on #1430. No pi client code in this layer.
Why
#1430 asked that spool/locking be shared instead of adding a third copy, that new memory-tool POSTs reuse the bounded JSON parser, and that MCP/viewer memory kinds share a core catalog. This PR extracts those primitives on current
mainso later pi layers can sit on them.What
createHookIngestSpoolused by claude and codex wrappers (same export names, same TTL/attempt numbers)parseJsonObjectBodyexported frompackages/viewer-server/src/helpers.tsmemory-kinds.ts: seven remember kinds +session_summary; MCP re-exports the sevenTest plan
vitest runon claude-hook-ingest-spool, claude-hook-ingest, codex-hook-ingest, helpers, memory-kinds — 63/63 passedtsc -b packages/cli --forcepassedStacked follow-ups (not in this PR): core pi adapter →
/api/pi-hooksalias → observer auth → CLI → extension → tools → setup/docs.Related: #1430, #1429