fix(memory): guard edits and writes and retain short search terms - #2235
Conversation
Codex ReviewVerdict: needs changes [P1] Recheck memory access before returning write resultssrc/main/memory/service.ts:316 Impact: After the transaction’s final check, snapshot publication can outlive Session revocation or disablement; the method then returns success without another access check, allowing a stale RPC to receive a 200 result after authorization expired. Recommendation: Run checkAccess immediately before every return, including after snapshot/event publication and on rejected-result paths. Summary: Found one authorization race in the Agent memory write path. |
Codex ReviewVerdict: needs changes [P1] Invalidate Memory operations when replacing an enabled provider sessionsrc/main/acp/session-aggregate.ts:111 Impact: Replacing a provider while Memory remains enabled leaves the existing AbortController live because setMemoryEnabled(true) is a no-op. Queued Memory RPCs from the old provider/control connection can therefore continue and commit after session replacement. Recommendation: Rotate or abort the Memory controller on every provider/session replacement, including detachProvider or attach when an existing provider is being replaced, then create a fresh controller for the replacement session. Add a replacement test with Memory enabled. Summary: Static inspection found one lifecycle defect in the new Memory authorization guard. |
Codex ReviewVerdict: mergeable No actionable findings. Summary: Static inspection found no concrete merge-blocking defects introduced by this pull request. |
Problem
Memory editors submit revision-1 drafts with a revision-2 snapshot's revision, silently overwriting another window's note or category changes (including auto-recall). Accepted Agent Memory RPC writes can also commit after their Session is disabled or connection capability revoked while queued. Multi-word search drops short ASCII scientific terms:
pHandNamatch individually whilepH Nareturns nothing.Proposed change
Scope and non-goals
No tables, columns, persisted fields, migration, historical-data conversion, or domain enum changes. Draft baselines and Session cancellation controllers are volatile. No automatic merging, forced overwrite, new queue, search engine, or authorization framework. Existing lost edits cannot be recovered by this change.
The final in-transaction authorization check is the commit decision: failure rolls back both insertion and revision; a transaction that already passed that decision may finish committing. Global enable/disable keeps its existing queue ordering.
Acceptance criteria and validation
Before production edits, five regressions failed twice for the reported reasons: two actual React forms sent revision 2, two actual HTTP RPC + SQLite cases returned 200 and inserted a row after disable/revoke (new requests returned 403/401), and SQLite search returned no
pH Namatch. A separate SQLite revision-control test passed and preserved external updates. The independent review also identified late write-result disclosure during post-commit snapshot publication. A new real SQLite regression failed twice before its fix; the service now checks access again after every successful/structured-rejection outcome leaves the queue. Committed data is retained while revoked callers receive an error. No production test seam was introduced. Deferred existing repository calls and call-through spies control concurrency without sleeps.Final checks, after the last material edit:
npm test --withmemory/service.test.ts,MemoryPanel.render.test.tsx,memory-store.test.ts,resources.test.ts, and ACPsession-aggregate,session-registry,session-replacement-workflow,task-agent-port,runtime-coordinator,prompt-preparation-owner,codex-turn-adapter,opencode-turn-adaptertestsnpm test -- src/main/notebook/local-rpc-server.test.ts src/main/notebook/local-rpc-server.capabilities.test.ts src/main/notebook/local-rpc-server.errors.test.ts src/main/notebook/local-rpc-server.frames.test.ts src/main/notebook/local-rpc-server.delegated-work.test.tsnpm test -- src/main/acp/runtime.test.ts -t 'Memory|memory'npm run typechecknpm run lintnpm run build:e2e;npm run test:e2e -- e2e/settings-persistence.spec.ts -g 'preserves Memory drafts'git diff --checkThe manually traced owner/consumer set above covers changed interfaces. Session cancellation is owned above provider adapters, shared by Claude Code, OpenCode, Codex Responses/bridge, and CodeBuddy; provider protocols and history replay are unchanged. Existing Session publication remains unchanged. No path/platform implementation or persisted schema changes. Local screenshots cover macOS Electron; other OS lanes and the complete portable suite remain PR CI's responsibility.
The impact classifier selects full fallback because the E2E file has unknown ownership. Per the maintainer's explicit request, no local full
npm testwas run and no classifier metadata was changed. Independent review ond6929a11is mergeable with no actionable findings. Exact-head PR Gate passed ond6929a11, including the complete portable suite, coverage, Linux isolation, Windows native/E2E, macOS native/E2E, static checks, and CodeQL.Provider replacement review disposition
Provider-only replacement is not capability revocation. Existing
NotebookLocalRpcServer.releaseSessionCapabilitiesexplicitly retains the Notebook RuntimeSession-owned control capability; only that owner'sconnection.release()revokes it. The ACP registry separately retains the app Session preference across provider detach/reattach. Aborting Memory on every provider attach would change that existing ownership contract.The added real HTTP + SQLite compatibility test replaces an enabled provider, calls actual ACP capability cleanup, and proves both the queued control request and a new request through the retained token remain authorized (200, created/existing, one row). The same parameterized test proves actual token release rejects queued/new calls, disable/re-enable cannot revive old operations, and HTTP disconnect prevents a queued write.
docs/PRD.mdnow states this ownership boundary explicitly. No additional production lifecycle change was made for this static-only finding.Review focus
Check that snapshots never advance draft baselines; the trusted execution check cannot come from request JSON; Session cancellation never survives re-enable; transaction cancellation rolls back revision with content; and no callback re-enters the Memory queue. The final-check/commit boundary is documented in
docs/PRD.md.CI retry evidence
On head
d6929a11, all Ubuntu portable shards, Windows checks/E2E, coverage, static checks, and independent review passed. The macOS job passed Electron/Web builds and all functional, workspace, accessibility, and visual journeys, including the new Memory conflict test. Its sole failure was the unchanged native sandbox testbinds concurrent approval requests to their owning command: the second curl returned code 7 instead of approved output.The exact four-file native group passed on both baseline
ee85abc4and the PR checkout (150 passed, 1 skipped each), and the isolated failed case passed locally. This does not establish the CI failure's root cause. One targeted rerun of the failed macOS job was requested without changing the head or rerunning passed jobs; the original failure is retained in the evidence.The single targeted macOS rerun passed every selected step and PR Gate finished successfully on unchanged head
d6929a11(run). No unrelated native-sandbox code was changed. The final independent review reported mergeable with no actionable findings (review).