Skip to content

fix(store): let every state registration fail for anything but "exists" - #424

Merged
nissa-seru merged 4 commits into
anima-research:mainfrom
nissa-seru:fix/state-registration-errors
Oct 18, 2026
Merged

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

Conversation

@nissa-seru

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

Copy link
Copy Markdown
Contributor

Registering a state now fails for anything but "already exists" (#385). Twelve of the seventeen places that register a state swallowed every failure, so a closed store or an I/O error went unnoticed there, and the caller met it later, at another call, under that call's name. This puts every registration through one helper that treats only "already exists" as done, and names the state in the error it throws.

!Fixes #385

What was wrong

isStateExistsError (src/module-registry.ts) says that only "already exists" is safe to swallow on an idempotent registration: "Anything else (closed store, IO error) must propagate". Three registrations used it, and the channel registry's two logs tested its message inline. The other twelve wrapped registerState in a bare catch. These are the eight sites #385 names and four more: the conversation router's state in create(), the tune-out coordinator's state read and write in the channel registry, and the workspace's tree and intent states.

Changes

  • ensureStateRegistered(store, registration), beside isStateExistsError, registers a state and rethrows every failure but "already exists" as Registering state '<id>' failed: <the store's message>, with the store's error as its cause.
  • All seventeen registrations go through it:
    • create(): the framework, deferred-write, coalescing, inference-log, turn-checkpoint, process-log and conversation-router states;
    • ModuleRegistry.addModule()'s module state, and a module's registerLogState();
    • CheckpointManager's checkpoint state, and a feature set's host state;
    • ChannelRegistry's lifecycle and label-history logs, and the tune-out coordinator's state;
    • the workspace module's tree and intent states, in initStore();
    • the tool-result guard's audit log.
  • A feature set's host state is registered inside the same try as the read of its stored value, in CheckpointManager's loadHostState. So a registration that fails gets the refusal a failed read gets: it names the state, says what the failure stops, before the server connects or when the feature set registers, and carries the helper's error as its cause and in its text. As on main, the feature set's tree is held only after both, so the next call tries again.
  • The workspace's initStore() registers every mount's states before it takes the store or any mount. It took them one mount at a time, so a failure at a later mount left the earlier mounts set, with no agreement, saved state or watchers.
  • registerLogState()'s doc says its cadence applies when the state is first registered.
  • Chronicle's error codes (feat(napi): the binding's errors carry a code from a new ErrorCode enum chronicle#31) aren't released, so the helper still matches the message. When they are, the change is to isStateExistsError alone.

What callers will notice

A failure other than "already exists" is now thrown by the call that registers the state, as an error that names it:

  • AgentFramework.create() rejects with it. A create() that fails partway stops what it had started and closes a store it opened, as on main since fix(create): a failed create() releases what it had acquired #426.
  • addModule() rejects with it.
  • The workspace's initStore() throws it, and leaves the module as it was. By reading, connectome-host calls initStore() without a catch (src/index.ts:678 at its main, 38b55da), so its startup fails with the error.
  • An MCPL server's checkpoint and host states, by reading at this head:
    • The checkpoint state is registered when MCPL starts, so create() rejects.
    • A restored feature set's host state is registered before its server connects, so create() refuses for a configured server, and connectMcplServer() for a later one, with the refusal a failed read of it gets.
    • A feature set registering for the first time has its host state registered in its server's connect. At startup that's logged as a failed connect while the server stays connected, with its later feature sets unregistered. In a runtime connect, the new connection is closed and connectMcplServer() rejects. In a reconnect, it's logged and the reconnect goes on.
    • The next call registers it, such as a reconnect's re-run, for a feature set whose host state couldn't be registered.
  • By reading, the tune-out coordinator's state throws it when the state is written, and a failed read of it is still taken as no dispositions, as under Not covered.

By reading chronicle 0.4.0, which this release depends on, the one failure these registrations swallowed there is a closed store, and the tests show a closed store's failure thrown. Before, the next call on that state failed under its own name, if one came. A registration the store answers with "already exists", as on any restart with an existing store, is still taken as done. By reading, a chronicle whose registerState can fail an existing state on checkpoint I/O (anima-research/chronicle#22) would make a restart fail at create(), addModule(), initStore() or an MCPL registration, where main swallowed it.

What each part is for

The change is for one thing: a state registration that fails for anything but "already exists" fails at the call that registers it, naming the state, instead of surfacing later at another call under that call's name.

  • ensureStateRegistered, one helper for all seventeen registrations: the rule isStateExistsError already stated lives in one place, and when chronicle's error codes are released, the change is to isStateExistsError alone. Weighed: putting isStateExistsError into each of the twelve catch-alls. That fixes the same sites, but leaves seventeen copies of one rule. The twelve came about that way, beside the five that already did it right.
  • The error names the state and keeps the store's error as its cause, so whoever meets the failure knows which registration it was, rather than reading the store's message alone.
  • One order changes, because a registration there can now throw (the workspace's initStore()): it took each mount before registering the next mount's states, a registration that swallowed its failure, so the order changed nothing. Now a failure there would leave earlier mounts taken with no agreement. Registering first lets the next call try again. A feature set's host state was already registered and read before its tree was held on main. Its registration moves into the read's try, so that its failure says what it stops, as a failed read does. The two latches, registerLogState()'s and the tool-result guard's, already registered first and rethrew on main, and their removals now pin that order.
  • registerLogState()'s doc says its cadence applies when the state is first registered, and that a registration that fails throws, naming the state, and leaves the name unregistered.
  • The test that src has no registerState call outside the helper: it reads the source rather than behaviour, and would fail a registration written correctly another way. It's kept because it's the only check that reaches a registration added later, which is how the catch-alls came about.

Cut before this pass: the round's first build also opened the workspace's disk agreement before initStore() took the store, so that a failed read of it left the module as it was too. No test pinned it, so it came out. Cut by this pass: nothing.

Evidence

On Linux, from a fresh install with chronicle 0.4.0, context-manager 0.14.1 and membrane 0.5.87.

At 5a8192983, this head. It merges main at 77066360 as bf27f3be1, then makes the host-state refusal change above:

  • The tests that reach these paths: 21 files (this PR's, boot-state-read-errors, the checkpoint, workspace, tool-result-guard and code-execution files among them) pass 784/784 at bf27f3be1, on Node 24.22.0 and on Node 20.20.2 with --test-force-exit. At 5a8192983, state-registration-errors, boot-state-read-errors, checkpoint-attribution and mcpl-reconnect-reregistration pass 71/71 on both. Typecheck and build are clean at both.
  • Red-first for the refusal change: with bf27f3be1's checkpoint-manager.ts and this head's tests, the two host-state registration tests fail, and so do the three that read the host-state refusal's text.
  • CI at this head: the four Build & Test jobs, Node 20 and 24 on Ubuntu and on macOS, each running the whole suite through npm test, and the packed-install check, all passed.

At 1c6e2058, before the merge, after an internal review of 13ddda9c:

  • The tests: test/state-registration-errors.test.ts passed 25/25 on Node 24.22.0 and on Node 20.20.2. It injects an I/O error into one registration at a time through each entry point above, and checks that the error names the state. It also checks the helper on both paths, against the store's own answer to a second registration and against a real closed store; that a feature set, a module's log state and the guard's audit state each register on the next call after a failed one; that a failed initStore() takes no mount; and that src has no registerState call outside the helper.
  • Red-first: with the first commit's three changed sources and that head's tests, 23 of the 25 failed: every test that reads a registration's error, since the error now names the state, and that round's new ones. The two that passed are the "already exists" test and the source check.
  • Removals: each of 8 pieces, taken out alone, failed a test. They are the helper naming the state and keeping the cause; the feature set recorded only after its registration; the mounts taken only after every registration; both latches, registerLogState()'s and the guard's, set only after their registrations; and neither of those two registrations swallowing its failure.
  • The full suite, each file run on its own: Node 24.22.0 unforced, 2,397/2,397 in 151 files. With --test-force-exit, Node 20.20.2 also counted 2,397/2,397, and Node 24.22.0 counted 2,379/2,379: it cut history-module-realstore (20 of 28) and history-module-ux (16 of 26) short, each of which passes whole unforced (npm test's --test-force-exit can end a test file before its tests report, so a short run still passes #294). Typecheck and build were clean.

At 13ddda9c, the first commit:

  • The tests: the file passed 20/20 on Node 24.22.0 and Node 20.20.2.
  • Red-first: with main's sources and only the helper added, 14 of the 20 failed. The six that passed pinned what main already did right: the helper's two tests, and the turn-checkpoint, module log, channel lifecycle and label-history registrations.
  • Removals: each of 19 pieces, removed alone, failed a test. Each of the twelve catch-alls, put back, failed its own entry point's test and the source check. Each of the five registrations that already rethrew, written back as its own correct try, failed the source check. The helper swallowing everything failed 18, and the helper refusing "already exists" failed its own test.

Not covered

  • Reads that turn a failure into empty state are a separate defect. By reading at this head, the tune-out coordinator's readDispositions caches {} after any failed read, and its next write puts that back. With this change a read's failed registration reaches that catch too; on chronicle 0.4.0 that failure is a closed store, where main's read failed as well. The conversation router's generation read swallows a failure too. Both are left for a change of their own.
  • An MCPL startup connect or reconnect whose setup fails after its connection is made: by reading, the server stays connected, with its later feature sets unregistered. That predates this change for any setup step after the connection, and is left for a change of its own.
  • addModule() keeps a module whose start() rejects, as it does for any start() failure. That's left for a change of its own.

Read by Gordon-2414 (Claude Opus 5.5). He read the change whole, the first commit and the round, at 1c6e2058, and checked the receipts above against the text. Then he read the merge of main (bf27f3be1, the remerge diff and this change against main) and the refusal change (5a8192983).

We won't merge this before 05:00 UTC on October 18, or before our pre-merge checks pass on its merge with the then-current main, and a line here asking us to wait holds it.

Written by Ada-1017.

Ada-1017 and others added 2 commits October 16, 2026 14:06
Twelve of the seventeen places that register a state wrapped
registerState in a catch that swallowed every failure as "already
registered". Only a state that already exists is safe to swallow (a
reopened store holds it). isStateExistsError said so, three sites used
it and two more tested its message inline, but the twelve also
swallowed a closed store or an I/O error. Each such failure then
surfaced at a later read or write of that state, under that call's
name.

ensureStateRegistered, beside isStateExistsError, registers a state and
rethrows every failure but "exists", and every registration in src now
goes through it:
- create(): the framework, deferred-write, coalescing, inference-log,
  turn-checkpoint, process-log and conversation-router states;
- ModuleRegistry.addModule() and a module's registerLogState();
- CheckpointManager's checkpoint state and a feature set's host state;
- ChannelRegistry's lifecycle and label-history logs (which tested the
  message inline) and the tune-out coordinator's state;
- the workspace's tree and intent states;
- the tool-result guard's audit log.

A failure is now thrown where it happened. Chronicle's error codes
(chronicle#31) aren't released, so the helper still matches the message;
when they are, it changes in one place.

Tests: each entry point fails with an injected I/O error for its state;
the helper accepts an existing state and rethrows anything else; and a
scan of src finds no registerState call outside the helper.
…ls leave nothing behind

From our internal review of the first commit.

The helper's rethrow now names the state, "Registering state '<id>'
failed: <the store's message>", with the store's error kept as its cause.
Before, create(), addModule() and initStore() rejected with the store's
bare message, which named no state.

CheckpointManager.registerFeatureSet registers a feature set's host-state
slot, and reads its stored value, before it records the feature set. It
recorded it first, so a registration that failed left the record behind,
and every later call, a reconnect's re-run included, returned at the
already-registered check without registering the slot or loading the
stored state.

The workspace's initStore registers every mount's states before it takes
the store or any mount. It took them one mount at a time, so a failure at
a later mount left the store and the earlier mounts set, with no
agreement, saved state or watchers.

registerLogState's doc says its cadence applies when the state is first
registered, and that a failed registration leaves the name unregistered.

Tests: the helper names the state and keeps the cause; a real closed
store's failure is thrown, not taken for "already exists"; the store's
own answer to a second registration is the one the helper takes as done;
a feature set registered after a failed call, with its stored state; an
initStore that fails takes no mount, and the next takes them all; a
module's log state and the tool-result guard's audit state, each
registered on first use, register on the next use after a failure. The
fragment says which two calls leave nothing behind, that the tune-out
coordinator's state fails where it's written, and that a failure
surfaced later only if a later call came.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5 Tier: apex

[Medium impact] The PR appears safe to merge; no new blocking issue was found.

Summary

Routes state registrations through ensureStateRegistered. Existing states remain a successful result; other failures name the state and retain the original error as their cause.

  • State registration failures now name the state and reach the call that registered it.
  • Workspace mounts are added only after all their states register.
  • Feature sets are recorded only after their host state is ready.

nissa-seru explicitly leaves failed-start cleanup (#389), reads that treat errors as empty state, partial MCPL setup, and modules retained after start() fails for separate changes. The source-only check is intentionally kept to catch later registrations outside the helper.

Tests were inspected, not run.

Reviews (2) · Last reviewed commit: "fix(store): a failed registration names ..." · Reviewed by Greptile

nissa-seru pushed a commit to ajaniramon/agent-framework that referenced this pull request Oct 17, 2026
…ir and the anchor for them

The internal review of this push found texts that still describe the old
marker set or say markers never influence routing, and pieces no test
pinned.

- The fragment's headline takes the maintainer's first sentence: markers
  "now preserve explicit-send suppression, like reaction markers". "No
  longer count as conversational input" said more than holds, since a
  marker that wakes the agent can still set that turn's speech channel
  (anima-research#189).
- isConversationalInjection's doc keeps its lines and changes only its
  last clause, which said markers "never influence routing". It now says
  they neither clear explicit-send suppression nor re-pin a turn's locus
  at a tool boundary, and that a waking marker can still set the turn's
  speech channel (anima-research#189).
- channel_publish's description and its channelId parameter say an
  omitted channel means the channel of the most recent incoming message
  that isn't a reaction, reaction removal, edit or deletion, which is
  what markAccepted tracks. The comments on defaultPublishChannel, its
  message id and thread, the reply anchor, the re-pin rule, the mid-turn
  suppression and the refusal reaction name the same four. The mid-turn
  signal's doc no longer says the locus is frozen: the signal itself
  never moves routing, and a tool boundary re-pins the locus by its own
  rule.
- The tags.js import moves below log-value.js, off the module-registry
  import line, which anima-research#424 and anima-research#428 change.
- Tests: the re-inference table gains rows for chat:reaction-remove,
  chat:edited, chat:deleted and an edit tagged chat:addressed, each
  leaving the turn's locus. The registry test asserts that a marker
  still repairs a channel's open state, that a marker on the anchor's own
  channel leaves the anchor, and that a real message after markers
  becomes the anchor with its own id and thread. The four mid-turn
  suppression cases, the reaction's included, assert that the marker
  reached the turn before its reply, since a marker that missed the
  round would leave the suppression set too.
@nissa-seru nissa-seru added the no-greptile For maintainers and the team: Greptile skips this PR. Not for general use. label Oct 17, 2026
Ada-1017 and others added 2 commits October 18, 2026 00:15
Two conflicts, both where main moved code this change also edits:

- src/mcpl/checkpoint-manager.ts: main (anima-research#454) builds a feature set's tree
  and reads its host-managed state through loadHostState before holding
  it. This change's registration-first order is kept by that structure:
  registerFeatureSet takes main's form, and loadHostState registers the
  state's slot through ensureStateRegistered instead of main's catch-all,
  so a failed registration throws naming the state and leaves no tree,
  at registration and before a connect alike.
- src/modules/workspace/index.ts: the import lines. main's observe.js
  names (anima-research#460) with this change's ensureStateRegistered import.

After the merge every registerState call in src goes through
ensureStateRegistered (17 sites, as before: main added none).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…what that stops

loadHostState's refusal says what a failed read of a host-managed state
stops: before a connect, that create() and connectMcplServer() don't go
on; at registration, that the server's connect fails. Its registration
ran ahead of that, so a failed registration threw only the helper's
"Registering state '...' failed: ...", naming the state without saying
what it stopped. It now runs inside the same try, and the refusal reads
"could not be registered or read", with the helper's error as its cause
and in its text.

The two tests that pin the refusal follow: the host-state registration
tests now check the refusal and its cause, and the read refusal's
expected text says "registered or read".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nissa-seru
nissa-seru merged commit e5a54ae into anima-research:main Oct 18, 2026
6 checks passed
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
main's anima-research#424 (state registrations) imports ensureStateRegistered from the
module registry in workspace/index.ts, beside this change's longer import
from observe.js. The one conflict is that import pair, and the resolution
takes both sides' names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
 and anima-research#430

main now holds anima-research#424 (F34), which this branch was stacked on, and anima-research#430
(F62), which shares one spawn step between the two spawn paths. Three
files conflicted:

- src/framework.ts: both spawn paths take anima-research#430's `await decision.spawned`.
  F50's counter store moves into spawnConversationAgent, first, inside the
  spawn's promise (an async IIFE: store, create, bind), so a store that
  throws rejects the spawn and each path's catch reports a failed spawn,
  rather than throwing out of routeConversation before either path's try.
  persistConversationRouterState has no caller left and goes.
- src/tune-out/coordinator.ts: anima-research#455 (F132) added describeThrown beside
  F50's thrownReason for the same reason. The merge keeps main's
  EXPIRY_RETRY_MS and note_disposition's key refusal, keeps thrownReason
  (it also covers a value whose instanceof check throws), and points
  F132's two callers at it.
- test/mcpl-rejection-log-lines.test.ts: each fake keeps F50's
  storeConversationGeneration stub beside anima-research#430's spawnStep().

test/tune-out-timers.test.ts merged cleanly, and its test of a value
with no string form now expects thrownReason's text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
…search#429's text

main moved through train G: anima-research#424, anima-research#430, anima-research#369, anima-research#427, anima-research#429 and anima-research#431. anima-research#429
rewords agent-framework's channel texts to say only what holds on every
MCPL server (anima-research#406), and this change lands second, so it carries anima-research#429's
text, as both pull requests' descriptions say.

Ten hunks conflicted, all with anima-research#429, and each is resolved in anima-research#429's words
under this change's rule (speechOpens decides what a reply does):

- The closed-channel invitation: anima-research#429's opening ("a channel you haven't
  opened. Messages there that address you, such as an @-mention, reach you
  whether or not you open it."), its option 2 ("Open the channel") and its
  `#` handling (hashLabel). Option 1 keeps this change's two cases. Where a
  reply opens the channel it says so in anima-research#429's words, "which opens the
  channel", then how to reply without opening it (decline first). Where
  the agent closed or declined the channel itself, it says "and the channel
  stays closed", with no follow-ups note: what else reaches the agent there
  is the server's to say (anima-research#406; anima-research/discord-mcpl#97 says it for
  discord-mcpl). The no-target hint keeps this change's send-tool advice,
  in the same words.
- channel_close's description: anima-research#429's two sentences, then this change's
  ("You can still speak there. It stays closed until you open it with
  channel_open: neither speaking there nor subscription policy reopens
  it.").
- The "[channels] Now open" notice: anima-research#429's middle sentence, then this
  change's ending.
- Doc comments: both sides' points, with option 1 described as speechOpens
  decides it. The send path keeps this change's comment, which replaced
  the INVARIANT comment anima-research#429 reworded.
- trunk-channel-routing.test.ts: both sides' tests. anima-research#429's
  invitation-label test's stub gains speechOpens, which this change's
  invitation calls.

Outside the hunks, the edits the same rule needs: this change's test of a
reply into a channel the agent closed now expects "and the channel stays
closed" (and is renamed), send-into-closed-channel's notice assertion
takes anima-research#429's middle sentence, and two comments say "open" where they said
"join". Train G's other merges touched framework.ts and the registry in
other functions; git merged them on their own (anima-research#369's text-block join in
dispatchMcplToolCall sits after this change's script note, and anima-research#429's
decline error message beside this change's refusal).
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
main had taken 24 merges since 86a3683, trains F and G among them:
anima-research#480 (deferred writes reach disk), anima-research#481 (stop()'s budget bounds every
wait), anima-research#457 (stop leaves nothing waiting), anima-research#454 (boot read errors),
anima-research#424 (state registration errors), anima-research#412 (one MCPL tool resolver), anima-research#369
(silent drops say so), anima-research#429 (channel texts across servers) and anima-research#430
(one conversation spawn).

Three conflicts:
- src/framework.ts: both sides add an import at the same place, anima-research#247's
  inbound-source types and main's IncomingMessageFacts. Both are kept.
  anima-research#454 replaced initializePushCoalescer with restorePushCoalescer, which
  initializeMcpl now builds before anything else, and installPushCoalescer.
  anima-research#247's inbound-acceptance block stays just before it, as it stood
  before initializePushCoalescer, and anima-research#247's accepted hook lands in
  restorePushCoalescer's options unchanged.
- src/mcpl/push-coalescer.ts: anima-research#247's accepted hook and main's new doc for
  the durability commit (one fsync per acceptance, under the lock). Both
  are kept.
- src/mcpl/push-handler.ts: anima-research#247's InboundSource import, and main's
  isVisiblyEmptyConversion (anima-research#369) in place of isVisiblyEmptyContent,
  which push-handler no longer uses.

One test needed the merge: anima-research#429's invitation test stands in a channel
registry with the four methods main's push lane called. anima-research#247's push
lane also names the channel in the message's source envelope, through
getChannelLabel, which anima-research#247 added to the registry, so the stand-in
gains it, reading the same labels as its getDescriptor.

None of main's new lines reads or writes anima-research#247's turn silence, its cause,
its drafts or its receipts. anima-research#457 ends a stopping framework's ephemeral
runs in stop() and its cancelled provider retries through
abortDroppedTurn, and has the event gate take a continuing turn's entry
again (onInferenceResumed) without touching a pending self-wake. anima-research#481
bounds stop()'s waits and keeps a still-streaming agent's deferred
writes queued. anima-research#247 reads none of these, and guardRetryTurnSilenced
keeps to tool-result-guard retries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
main at d9bd6dc brings anima-research#460, anima-research#462, anima-research#466, anima-research#424, anima-research#445 and anima-research#425 into code
this change rewrites. The resolutions:

- src/modules/workspace/index.ts:
  - anima-research#460: editablePath parses a path with parseFilePath, so edit and
    edit_many refuse a path in a nested mount as every file tool does,
    and applyEdits' pass names the paths it settles ({ named: true }), so
    a file the mount passes over by design is taken in as a named sync
    takes it.
  - anima-research#462: commitToolChanges answers each path's kept mode beside its
    refusal, as commitToolChange does for one path. edit's answer carries
    it as modeKept, and edit_many's in each file's entry. applyEdits
    leaves a mode taken in alone unsaid, as handleEdit did, and the edits
    keep the entry's mode, as before.
  - anima-research#466: applyEdits already words the conflict note's advice by what
    disk holds now, so this change's side stands.
  - anima-research#424: the imports keep both sides.
- src/framework.ts, src/module-registry.ts and src/types/events.ts: anima-research#445's
  callerTurnId beside this change's resultMaxChars. The utils call
  carries both, the registry passes the call on whole, callerTurnId
  included, and events.ts documents both. anima-research#425's comment on a script's
  result keeps this change's SCRIPT_TOOL_RESULT_MAX_CHARS.
- test/workspace-file-tools.test.ts: this change's expectations, which
  already carry anima-research#466's advice in the refusal's new wording.

Also in this commit, as Anarchid's review asked (R1-4): edit_many cases
in the two nested-mount tests, which assert the nested-mount refusal's
exact text for edit and edit_many, since the named pass's note names the
inner mount too, a test of edit_many naming files the mount passes over
by design, and edit_many in the test of a push that keeps
disk's mode, all in workspace-three-way. anima-research#460's ignore-list adoption test
and anima-research#466's three deletion tests now expect the edit's refusal wording.
The read-back's mode test calls the commit directly, since an overwrite
now keeps the entry's mode and no tool sets the same bytes under another
mode. planFile's doc gives the text's length as how long it is, not its
characters, and the changelog says edit_many carries modeKept.

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

greptile no-greptile For maintainers and the team: Greptile skips this PR. Not for general use.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

State-registration catch-alls swallow I/O errors, contrary to isStateExistsError's own contract

1 participant