Skip to content

fix: keep retaining a source after an earlier memory's page moved - #244

Merged
tenphi merged 1 commit into
mainfrom
fix/retain-moved-support-page
Oct 20, 2026
Merged

tenphi merged 1 commit into
mainfrom
fix/retain-moved-support-page

Conversation

@tenphi

@tenphi tenphi commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Summary

Before writing a new revision of a source, automatic retention compares it with the earlier memories from the same source group (assessEarlierSupport). For each earlier memory it reads the page the memory lives on now, from the managed_memory_entries projection, and also every retain_supports.slug. That slug records where the memory was first written. retention-source-frame.ts notes that it deliberately survives a page move.

So after maintenance or a person moves that page, folds it into a folder of the same name, or removes it, read throws not_found. Nothing in that loop catches it, so the error reaches the catch-all in retain():

  • the whole revision is held with degraded: ['retain_apply_failed'], reason_code: 'apply_failed', and the note source apply failed: no page at <original slug>;
  • every later revision of the same source fails the same way, because the original slug stays on the support row.

This showed up in production as a conversation whose digests stopped saving anything after its earlier memories were reorganised.

Change

A not_found from that read now stands for a page that carries no lines:

  • the earlier memory is still compared on the page where it lives now;
  • one that no page carries any more is skipped, which is what the comment after the loop already intended;
  • any other read error still fails the revision, so an unreadable page is not mistaken for an absent one.

retain_supports.slug is left as the original placement on purpose. Rewriting it on move would change what retention-source-frame.ts relies on.

Not changed

stageRetractions also locates a retraction target by support.slug. It does not throw. For a moved memory it holds the correction as source_unavailable ("owned page is unavailable") instead of retracting at the new location. That is a separate limitation and is left for its own change.

Tests

  • New regression in packages/core/test/retain.test.ts: retain a memory into a conversation group, move its page, then retain an extracted revision of the same group. It checks that the revision is ok with no degraded reasons, and that the earlier memory is compared where it lives now. Before the fix it reproduced the production note exactly.
  • pnpm typecheck, pnpm lint, pnpm check:repository and prettier pass. retain.test.ts passes 65/65.
  • Full pnpm test: 5022 passed, 6 failed. All 6 were document-ingest/OCR tests that timed out at 30 s under full-suite load (ingest.test.ts, extractor-contract.test.ts). Run alone they pass 76/76.

🤖 Generated with Claude Code

Comparing a new revision with earlier memories from the same source read
every page those memories were first written to, beside the page each
lives on now. The support slug deliberately records the original
placement, so after a move, a fold into a folder or a removal that page
no longer exists, and read threw not_found. Retain caught it as
retain_apply_failed and held the whole revision, and every later
revision of the source failed the same way.

A page that no longer exists now contributes no lines. The earlier
memory is compared where it lives now, one that no page carries any
more is skipped as before, and any other read failure still fails the
revision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tenphi
tenphi merged commit 3bb2033 into main Oct 20, 2026
3 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant