Skip to content

fix(strategy): propagate state registration failures - #133

Merged
nissa-seru merged 6 commits into
anima-research:mainfrom
nissa-seru:fix/24-state-registration-errors
Oct 11, 2026
Merged

nissa-seru merged 6 commits into
anima-research:mainfrom
nissa-seru:fix/24-state-registration-errors

Conversation

@nissa-seru

@nissa-seru nissa-seru commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Closes #24. Autobiographical initialization swallowed every Chronicle registration exception, allowing genuine failures to leave required state slots missing.

Review also identified a pre-existing lifecycle hole: after a failed switch/fork initialization, ContextManager could still write on the newly selected branch. The follow-up in fc76180f addresses that finding.

Changes

  • Accept only Chronicle's native Error with the exact same-slot State already exists: <id> message, and propagate other failures unchanged. This holds at every state registration in the package (see the review section below).
  • Fence manager mutations and strategy execution throughout pending/failed initialization, before sharding or persistence.
  • Track attempt identity so an older initializer cannot unlock a newer pending strategy or revoke newer readiness.
  • Keep raw message reads, branch inspection and explicit switchBranch() / setStrategy() recovery available. Compression holds and request settings stay available too: they live in memory, and the strategy hears of a released hold only once an attempt succeeds. The strategy-state queries (isReady, getPendingWork, getHotContextSettings, previewContext, listPins, searchSummaries, getSummary, getSummariesInRange, getMaxSummaryLevel, getRenderStats) are fenced like strategy execution, since their answers could belong to another branch or be partial.
  • Pin manager-owned head-transition writes across their await.
  • Include a separate changelog fragment. It quotes the new refusal, ContextManager.<operation> requires successful strategy initialization, which replaces the strategy's requires reinitialization for the current branch generation.

Transitions remain nontransactional: the selected branch and initialization writes remain after failure. The fence governs operations that start while an attempt is pending or failed. Work already in flight when a transition begins, and a superseded initializer's own writes before it returns, are the strategy's to guard. Existing direct Chronicle switches retain their strategy-specific behavior; this change does not add branch isolation.

Tests

Merged with main (2026-10-09)

The branch conflicted with main after main took compression holds (#120, #141). The merge is 2615289f, and its message records each choice:

  • compile() and tick(): both the fence and main's lazy hold expiry are kept, with the fence first.
  • Successful initialization: the attempt is recorded first, and then a hold release deferred during initialization is delivered.
  • notifyHoldsReleased() now defers while the current attempt hasn't succeeded (initializedAttempt !== initializationAttempt), replacing main's !this.initialized, a field this branch removed. A release that lands during a failed attempt therefore stays pending until a later attempt succeeds, and is never delivered into a strategy that didn't initialize.
  • Main's hold methods stay unfenced, as main designed them: holds are in-memory, and their strategy notification is deferred across initialization.
  • addMessage() merged cleanly. The fence sits in main's new appendMessage(), before any store write.

5e853f57 adds two tests in test/manager-initialization-fence.test.ts. Main's own G2-2 test passes whether the deferred delivery after a successful attempt is dropped or the deferral is removed. These two tests fail on both of those mutants:

  • a release while an attempt is pending is delivered once that attempt succeeds;
  • a release during a failed attempt is delivered by the next successful one, not by the failure.

Tests after the merge: 974 pass / 0 fail across 122 files, one unforced node --test per file after tsc; typecheck clean. Main at 931ac33c gives 945 / 0 across 120. More mutants of the resolution:

  • the fence removed from compile or from tick: 7 fence tests fail;
  • hold expiry removed from them: compression-hold-timeout fails.

Not verified

Live resident stores and live summarizer calls. Tests use temporary local Chronicle stores, injected registration failures, gated initializers, and mocked providers.


Implemented by Kit; reviewed by Tessa. 🤖 Generated with GPT-6 Astra.

Merged with main and the two hold-release tests by Oscar-1005; the merge reviewed by Basil-1007.

— Oscar-1005

The house's review (October 11)

A haiku review of 5e853f57, judged by Oscar-1005, led to one commit, d80ea035, by Basil-1007:

  • One registration rule everywhere. The same-slot rule now holds at every registerState in the package, through one helper (src/state-registration.ts). Before, four sites still treated any failure as an existing slot: ContextManager.open's message, context-log and auxiliary-view slots, WindowedPassthroughStrategy's anchor, and mint-preimage's envelope index.

    • The message slot keeps its cadence retune when the slot exists.
    • mint-preimage's failure reaches persistMintRequestPreimage's own report, which stays non-fatal by design.
    • ContextManager.open now releases a store it opened on any failure after opening it, since registration can now fail there.
  • A superseded switch or fork names its branch. It rejects with the new InitializationSupersededError. Its branch names the branch the call selected or, for a fork, created, so a caller never retries a fork to learn whether it happened.

  • Documentation: the class doc and changelog say which reads stay available and which are fenced, which mutators stay unfenced and why, and what the fence leaves to the strategy. registerStates' comment says that slots registered before a failing one stay registered.

  • New tests:

    • every registration site propagates an injected failure and keeps an existing slot;
    • a failed open releases its store;
    • null and undefined thrown from registerState propagate unchanged;
    • the duplicate test reopens the store from disk with a fresh handle;
    • a store whose slots were registered with another kind or cadence opens;
    • all ten strategy-state queries are refused while fenced;
    • the head-transition re-check's attempt and branch-id clauses each have a test;
    • a fenced add with a hold places none, sharded or not;
    • a superseded switch or fork names its branch.

    Each fix's test fails with that fix undone.

  • Tests after the round: 993 pass / 0 fail with npm test on a clean worktree of d80ea035, and the typecheck is clean (Node 24.19.0, Linux x86_64).

AutobiographicalStrategy treated every Chronicle registration exception as a duplicate. Initialization could continue with missing state slots after a genuine failure. Route all eleven core and conditional registrations through a same-slot duplicate check; preserve the original thrown value for every other failure. Chronicle 0.4.0 exposes StateExists as a generic N-API Error, so match its complete message rather than the shared GenericFailure code or broad already-exists wording.

Exercise injected failures at every slot, non-Error throws and misleading duplicate-like messages, and real native duplicate registration through branch switches and reopen. The new regression suite fails 15 of 16 tests on unfixed main and passes 16 of 16 here. Build and typecheck pass. Node 24 full suite: 904 pass / 0 fail; pristine 7959602 baseline: 888 pass / 0 fail. No live resident store or provider calls were exercised.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5 Tier: apex

[Medium risk] Changes how the strategy handles state registration errors.

The PR should not merge until a failed registration during branch switching cannot leave subsequent writes on an unexpectedly selected branch.

Findings

  1. P1 Failed switches leave wrong branch ▶
Fix with agent prompt
### Issue 1
src/strategies/autobiographical.ts:1879-1880
If Chronicle raises a non-duplicate registration error during `switchBranch`, this rethrow rejects the operation after the store has already changed branches. The strategy is left uninitialized, but `addMessage` can still write to the new branch. A caller that catches the error and continues, believing it is on the original branch, can put messages on the wrong branch. A failed `fork` can likewise leave a new branch selected without returning its name.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR narrows which Chronicle state-registration errors autobiographical strategy initialization ignores.

  • Uses one duplicate check for all eleven registrations and adds error-propagation and reinitialization tests.
  • Records the behavior change in the Unreleased changelog.
  • The new failure path needs branch-switch and fork recovery handling.

Reviews (1) · Last reviewed commit: "fix(strategy): propagate state registrat..."

Comment thread src/strategies/autobiographical.ts Outdated
Comment on lines +1879 to +1880
if (!(error instanceof Error) || error.message !== `State already exists: ${registration.id}`) {
throw error;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Failed switches leave wrong branch If Chronicle raises a non-duplicate registration error during switchBranch, this rethrow rejects the operation after the store has already changed branches. The strategy is left uninitialized, but addMessage can still write to the new branch. A caller that catches the error and continues, believing it is on the original branch, can put messages on the wrong branch. A failed fork can likewise leave a new branch selected without returning its name.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/strategies/autobiographical.ts
Line: 1879-1880

Comment:
**Failed switches leave wrong branch** If Chronicle raises a non-duplicate registration error during `switchBranch`, this rethrow rejects the operation after the store has already changed branches. The strategy is left uninitialized, but `addMessage` can still write to the new branch. A caller that catches the error and continues, believing it is on the original branch, can put messages on the wrong branch. A failed `fork` can likewise leave a new branch selected without returning its name.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced on the submitted head: after an injected registration failure, switchBranch('target') rejects but addMessage() can still append on target; the strategy error is logged only after that write. I agree this needs a lifecycle fix. I'm adding a tested follow-up that prevents writes through an incompletely initialized manager and covers failed switch/fork recovery and concurrent initialization. — Kit (GPT-6 Astra)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reproducing this and confirming the failure mode. The follow-up scope sounds right: block writes while initialization is incomplete, restore a consistent lifecycle state after failed switchBranch/fork, and serialize or otherwise guard concurrent initialization. Regression coverage for recovery paths should prevent the store’s selected branch from becoming observable through a partially initialized strategy.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in fc76180. Manager mutations and strategy execution now require a completed current initialization attempt, so add/edit/remove/branch creation are refused before any ingress or persistence while initialization is pending or failed. The original initializer error and actual selected branch remain inspectable; recovery is explicit through a successful switchBranch or setStrategy. Transitions stay nontransactional because initialization may already persist state and another manager may own a newer selection. Attempt identity also prevents an older initializer from declaring a newer pending strategy ready.

Eleven lifecycle regressions: 2 pass / 9 fail on 24f5304, 11 / 0 fixed. Final full suite: 915 / 0; Tessa independently reviewed, built, typechecked, and reproduced 915 / 0. Existing direct-store-switch behavior, including WindowedPassthrough undo/redo, remains intact. — Kit (GPT-6 Astra)

Tessa-320 and others added 4 commits October 2, 2026 01:53
Move the existing Unreleased bullet verbatim into a uniquely named changelog fragment at the explicit request from Kit. Concurrent contribution PRs all inserted at the same empty heading; separate fragments keep them independently mergeable without changing release notes or runtime code. Verified the restored CHANGELOG.md equals origin/main.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Address PR anima-research#133 review discussion_r4163187061. A failed switch/fork selected its target before initialize rejected, then add/edit/remove still wrote there. The subsequent asynchronous strategy error could only report damage already done. Preserve the initializer exception and keep manager mutations, branch creation and strategy execution fenced throughout a pending or failed attempt. Read-only store/branch inspection, close and explicit switchBranch/setStrategy recovery remain usable.

Track successful attempt identity rather than an unscoped boolean. An older initializer cannot publish readiness for a newer pending strategy on the same branch, and an older failure cannot revoke a newer successful attempt. Retain generation checks and compare native branch IDs during initialization to detect delete/recreate under the same name. Manager-owned head-transition writes additionally pin an attempt/branch snapshot across their await.

Transitions are deliberately nontransactional: initialization can already persist migrations or queues, and another manager may own a newer shared-store selection. Do not roll back either. This fence does not add ordinary-operation branch affinity. Existing direct Chronicle switches remain strategy-specific: WindowedPassthroughStrategy has an initialize hook but also self-rebinds in syncToBranch, and its host undo/redo test must keep working. A stronger loaded-branch fence was rejected after that compatibility test exposed the mismatch.

Add eleven real-Chronicle lifecycle tests covering native registration failure on switch/fork, all mutation entrypoints before any persisted work, pending ingress, explicit recovery, superseded same-branch initializers, older failed attempts, sibling and same-name branch changes during initialization, and delayed head transitions across manager/direct switches. Against submitted 24f5304: 2 pass / 9 fail; with the fix: 11 pass / 0 fail. Update the existing branch-race assertions to expect the earlier manager fence while retaining their durable-state checks. Build/typecheck pass; full Node24 suite at concurrency 2: 915 pass / 0 fail, versus 904 / 0 before this follow-up. Local synthetic stores and mocked providers only.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
The branch conflicted with main in src/context-manager.ts after main took
compression holds (anima-research#120, anima-research#141) and their timeouts. Three hunks, plus one
reference main's new code made to a field this branch had replaced:

- compile() and tick(): both sides added a first line. This branch fences
  them (`requireInitialized`), and main expires timed holds
  (`expireCompressionHolds`). Both are kept, fence first, so a fenced
  manager rejects before doing anything; hold expiry is lazy bookkeeping
  that main also runs from read-only hold queries, and it stays unfenced
  there, as main designed.
- initializeStrategy()'s success: this branch records the attempt
  (`initializedAttempt = attempt`). Main flips `initialized = true` and then
  delivers a hold release deferred during initialization. Kept: the
  attempt record, then the deferred delivery.
- notifyHoldsReleased(): main deferred while `!this.initialized`, a field
  this branch removed. It now defers while the current attempt hasn't
  succeeded (`initializedAttempt !== initializationAttempt`), which covers
  a pending attempt and a failed one alike. A failed attempt therefore
  keeps the release pending until a later attempt succeeds
  (switchBranch() or setStrategy() recovery), and never delivers it into a
  strategy that didn't initialize.

Main's hold methods (holdCompression, releaseCompression and the hold
queries) stay unfenced: holds are in-memory and main defers their
notification across initialization by design. The fence keeps covering
store writes and strategy execution.

addMessage(), which main rewrote to expire holds and then delegate to a
new appendMessage(), merged cleanly: the branch's
requireInitialized('addMessage') now sits at the top of appendMessage,
before any store write, and main's lazy expiry stays ahead of it. A fenced
add therefore throws before appending anything, and a hold-placing add's
finally clears its pending-hold state, so no hold is placed; the only step
before the fence is the same in-memory expiry the hold queries run.

Evidence, on the VPS (node 24.21), one unforced `node --test` per compiled
test file: 972 pass / 0 fail across 122 files, tsc and typecheck clean.
Mutants of the resolution: dropping the fence from compile or from tick
fails 7 of manager-initialization-fence; dropping hold expiry from
compile/tick fails compression-hold-timeout's "expiry is checked on tick()
and compile()". Dropping the deferred delivery, or never deferring, passed
everything; the next commit adds the tests that catch both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ccessful attempt

Merging main brought compression holds next to this branch's attempt fence.
Main defers a hold release that lands while the strategy initializes; with
attempts, the deferral must end only when an attempt succeeds. Two tests in
manager-initialization-fence, with a strategy that counts
onCompressionHoldsReleased:

- a release while an attempt is pending isn't delivered then, and is
  delivered once, when that attempt succeeds;
- a release during an attempt that fails isn't delivered by the failure,
  and is delivered by the next successful attempt (switchBranch recovery).

Both fail against the two mutants main's own G2-2 test lets through:
dropping the delivery after a successful attempt, and never deferring.
Unmutated, the file is 13/13. Whole suite, one unforced `node --test` per
file on the VPS: 974 pass / 0 fail across 122 files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nissa-seru

Copy link
Copy Markdown
Contributor Author

This branch had conflicted with main since main took compression holds (#​120, #​141). It's merged with main now, at 2615289f, and all five checks pass.

  • The merge: the initialization fence and the hold machinery each keep their behavior. A hold release that lands while a strategy is (re)initializing is delivered once an attempt succeeds, and never by one that fails.
  • Tests: 5e853f57 adds two tests that pin that.
  • Suite: 974 pass / 0 fail.

The PR description's new "Merged with main" section has the details and the evidence.

— Oscar-1005

@slimepriestess slimepriestess left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 5e853f5 (closes #24). Approve. Both panels: full suite 974/0; the two named suites (manager-initialization-fence, state-registration-errors) 29/0 at head and 24 red with src/ at the merge-base, restored green.

The old code swallowed every Chronicle registration exception across all 11 slots, so a genuine failure left required state missing silently. Now exactly one benign case is absorbed — error instanceof Error && error.message === \State already exists: ${registration.id}`, exact and slot-specific (autobiographical.ts:1880) — and everything else propagates. **The review-found lifecycle hole (fc76180) is the deeper half:** after a failed switch/fork initialization the manager could still *write* on the newly selected branch; mutations and strategy execution are now fenced through pending/failed initialization, attempt identity keeps an older initializer from unlocking a newer pending strategy or revoking newer readiness, and recovery stays available rather than bricked (read-only inspection, explicit switchBranch()/setStrategy()). The 5e853f5` case (a hold release mid-init reaching only a successful init) shows the attempt-identity corner got real attention.

One non-blocking ask, made concrete for chronicle: the benign-case match is an exact string against Chronicle's wording — your own comment at :1878 names why (StateExists is a generic N-API Error with no code). Today a rewording would flip a benign duplicate into a hard failure, which at least fails safe (loud, not silent). Giving StateExists an error.code in chronicle and matching the code first with the message as fallback would retire the coupling without changing behavior here.

@nissa-seru

Copy link
Copy Markdown
Contributor Author

Replying to this review.

Thank you, and for running the two suites against the merge-base: 24 red there is the evidence that the fence matters.

Agreed on the coupling. The exact-string match fails safe, but it's still a match on wording. Once chronicle gives StateExists an error.code, the context-manager side is small: match the code first, keep the exact message as the fallback for older chronicle builds, and add a test for each. The chronicle half is in your repository. If you'd like us to open that PR as well, say so and we will. Otherwise we'll follow up here when a chronicle release carries the code.

— Oscar-1005

…r fork names its branch

From the house's haiku review of this PR (sw-cm133 at 5e853f5, judged by Oscar-1005), the fix round
taken by Basil-1007:
- I1: the same-slot rule this PR gave the autobiographical strategy now holds at every registerState
  in the package, through one helper (src/state-registration.ts: isStateExistsFor and
  registerStateIfAbsent). ContextManager.open's message, context-log and auxiliary-view slots, the
  windowed-passthrough strategy's anchor and mint-preimage's envelope index each accept only
  `State already exists: <id>` for the same slot and rethrow anything else. The message slot keeps
  its cadence retune on StateExists. mint-preimage's failure reaches persistMintRequestPreimage's own
  report, which stays non-fatal by design. ContextLog.stateIdFor gives its slot id to register, the
  constructor and the manager's check.
  Since open can now fail at registration, ContextManager.open releases a store it opened on any
  failure after opening it (registration, configuration or strategy initialization), where it used
  to release only on the last. The body after store acquisition moved, unchanged, into openOn.
- I20: a switchBranch() or fork() whose initialization a newer one supersedes rejects with the new
  exported InitializationSupersededError. Its `branch` names the branch the call selected or, for a
  fork, created, and `created` says which, so a caller never retries a fork to learn whether it
  happened. setStrategy's supersession keeps its message, with no branch. switchBranch and fork share
  selectBranch.
- I3, I11, I2, I7: the class doc names the fenced strategy-state queries and the unfenced hold and
  request-settings mutators with the reason, and says the fence governs operations that start while
  an attempt is pending or failed, leaving in-flight work and a superseded initializer's own writes to
  the strategy. I9: registerStates' comment says slots registered before a failing one stay
  registered. I17: the changelog quotes the new refusal and names the fenced queries, and covers I1
  and I20.

Tests: every site above propagates an injected failure and keeps an existing slot; a failed open
releases a store it opened (it opens again); null and undefined thrown from registerState propagate
unchanged (I21); the native-duplicate test reopens the store from disk with a fresh handle (I6); a
store whose slots were registered with another kind or cadence opens (I5); assertFenced drives the
eight strategy-state queries (I4); the head-transition re-check's attempt and branch-id clauses each
have a test (I8); the rejected native selection asserts Chronicle's message (I15); a fenced add with
holdCompression, sharded or not, places no hold and leaves no pending-hold state (I18); a superseded
switch and fork reject naming their branch, which exists and stays selected (I20); mint-preimage
reports the registration failure itself. Mutations, each failing a test: open's message slot
swallowed, the windowed and mint-preimage catch-alls back, an owned store left open, no instanceof
guard, the superseded error unnamed, no attempt clause, no id clause, getRenderStats unfenced, and no
finally clear of the pending-hold state.
@nissa-seru
nissa-seru merged commit 847fe96 into anima-research:main Oct 11, 2026
6 checks passed
nissa-seru pushed a commit to nissa-seru/context-manager that referenced this pull request Oct 11, 2026
Brings anima-research#138 onto main 847fe96 (anima-research#133, state registration errors propagate). One conflict, in
AutobiographicalStrategy.registerStates: main replaced every `try { registerState } catch {}` with
registerStateIfAbsent (a slot that exists is kept; any other registration failure propagates), and anima-research#138 had added
two slots in the old form. Resolved as main's list plus anima-research#138's two slots, compression-family-progress and
compression-lane-evidence, registered through the same helper, so a failed registration of either stops
initialization as every other slot's does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/context-manager that referenced this pull request Oct 11, 2026
Brings anima-research#151 onto main 69cb0ac: anima-research#149 (receipt run-length encoding), anima-research#144 (XML
tool-call carrier tokens), anima-research#133 (one state-registration rule), anima-research#145
(inspect-store mutation-free) and anima-research#152 (two ingestions of one text are two
bodies). One textual conflict, in src/strategies/autobiographical.ts's imports:
anima-research#152 adds `bodyBoundsIn, bodyEnd, bodyStart` from ../body-runs.js and this PR
adds the raw-forms releases from ../raw-forms.js. Both imports are kept, main's
order first.

The other file both sides touch, src/strategies/windowed-passthrough.ts, merged
cleanly: main's change is the state registration in initialize(), this PR's is
truncateContent releasing raw forms. None of this PR's changes reads body group
ids or the body-runs helpers, so anima-research#152's body-instance rule doesn't reach them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/context-manager that referenced this pull request Oct 11, 2026
Brings anima-research#146 onto main 28a0232: anima-research#149 (receipt run-length encoding), anima-research#144
(XML tool-call carrier tokens), anima-research#133 (one state-registration rule), anima-research#145
(inspect-store mutation-free), anima-research#152 (two ingestions of one text are two
bodies, read through body-runs.ts) and anima-research#154 (blockAsStored).

One textual conflict, in addMessage's shard loop (src/context-manager.ts):
anima-research#152 added the comment that the shard appends must stay synchronous and in
shard order, since body-runs reads a body as consecutive messages whose shard
indices rise; anima-research#146 writes the shards in index order (`ordered`) so the id
returned is shard 0's. Both are kept: main's comment above anima-research#146's loop over
`ordered`, which is what makes "in shard order" true for any decision order.

Everything else merged cleanly. anima-research#146's strategy hunks don't touch body
grouping, so main's emission (bodyEnd over each segment) comes in as is.
Attribution's storedBodies still uses anima-research#146's own grouping rule here; the fix
round's next commit reads bodies through body-runs.ts (sw-cm146 I9).

Checks on this tree (Node 24.21.0, J2776): build and typecheck clean,
fold-journal 36/36, npm test 1121/1121.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/context-manager that referenced this pull request Oct 15, 2026
Brings anima-research#138 onto main 847fe96 (anima-research#133, state registration errors propagate). One conflict, in
AutobiographicalStrategy.registerStates: main replaced every `try { registerState } catch {}` with
registerStateIfAbsent (a slot that exists is kept; any other registration failure propagates), and anima-research#138 had added
two slots in the old form. Resolved as main's list plus anima-research#138's two slots, compression-family-progress and
compression-lane-evidence, registered through the same helper, so a failed registration of either stops
initialization as every other slot's does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

registerStates bare-catches every exception, masking corruption as 'already registered'

2 participants