claude-code: a session's memory follows the operator to every node (issue 367, ADR 0343) #276

Merged
mesh-admin merged 4 commits from fix/367-memory-follows-the-operator into main 2026-10-13 14:26:59 +00:00
Contributor

Fixes issue 367 (a session's memory stays on the node where it was learned). Decision: novox/hq ADR 0343 (proposed, needs the operator's Approve); its home is design 36 §12 (hq PR on branch design/367-memory-follows-the-operator).

What

  • memory.go: the module's long-running process carries each project's memory folder, ~/.claude/projects/<project>/memory/, and nothing else of the home, through a new kept state memory (one key per home kind and file; the text, its digest, the digest it replaced, the node, the change time).
    • Picked up every 10 s; a file is read again only when its size or time changed.
    • Written on another node only where its file is still the version the change replaced, or absent; staged in the folder and renamed in, never through a symbolic link; the version replaced is kept first in memory-copies/ in the module's state directory (30 days).
    • Every write to the state is at the revision read (create / compare-and-set), and a change handed over late is applied as the state's current version, so two nodes never merge conflicts back and forth.
    • A file changed on two nodes becomes one file with both versions, the newer first, then a line naming each node and time, then the older; it is carried, and the operator is warned through seat:operator-channel.notify (warning, keyed per file, no path in the words; cleared when the file next changes without a conflict).
    • A removal is a marker value (never a deleted key, kept 30 days): an unchanged file elsewhere moves into the copies, a changed one is kept and carried again. A missing folder, a key the state lost, and a deleted key remove nothing.
    • Homes of the two kinds (the operator's own, an agent account's own) never take each other's memory; a confined agent in a home not its own carries nothing.
    • Not carried, and listed with why: symbolic links, files over 128 KiB, files with a key block, a signed token, a known token prefix or a password in an address (narrower than the SDK's check on purpose: notes name commit hashes).
  • claude_code_memory: read-only, fingerprints never text.
  • Manifest: state memory, tool claude_code_memory. README: the new section and the NATS row.
  • No tool writes a memory file or the state (issue 340's boundary).

Tested

  • New memory_test.go (temporary homes, a state in memory with the bus's watch semantics, never the live folders): carried unchanged and only once; only memory folders; exclusions listed; the two home kinds apart; conflict kept twice newer first, one warning, cleared on tidy, both versions in the copies; removal moves the unchanged file into the copies; a change outlives a removal in either order; missing folder / lost key / deleted key remove nothing; first carrying keeps both indexes and each node's own files; nothing written through a symbolic link; manifest declares the state.
  • On main the test file does not build (MemoryCarrier undefined); with the fix the package passes, also with -race -count=3 on the memory tests.
  • MESH_GATE=<controller built from mesh-controller main> MESH_CHECK_MODULES=claude-code sh merge-check.sh passes.

Acceptance

A memory file written on g14 appears unchanged on shanks within a minute (claude_code_memory on shanks lists it with g14's fingerprint), and a file changed on both before either carried it is kept twice in one file, newer first, with one warning on the operator's channel.

Rollout note

On the first look after delivery, every node carries its existing memory; where two nodes hold different versions of one file (MEMORY.md above all), both are kept in it and one warning per such file is sent. Nothing is lost: each node's prior version is also in its memory-copies/.

Left open, tracked: a value written straight into the state by a holder of a tool runner's credential is not told apart (issue 638).

Fixes issue 367 (a session's memory stays on the node where it was learned). Decision: novox/hq ADR 0343 (proposed, needs the operator's Approve); its home is design 36 §12 (hq PR on branch design/367-memory-follows-the-operator). ## What - `memory.go`: the module's long-running process carries each project's memory folder, `~/.claude/projects/<project>/memory/`, and nothing else of the home, through a new kept state `memory` (one key per home kind and file; the text, its digest, the digest it replaced, the node, the change time). - Picked up every 10 s; a file is read again only when its size or time changed. - Written on another node only where its file is still the version the change replaced, or absent; staged in the folder and renamed in, never through a symbolic link; the version replaced is kept first in `memory-copies/` in the module's state directory (30 days). - Every write to the state is at the revision read (create / compare-and-set), and a change handed over late is applied as the state's current version, so two nodes never merge conflicts back and forth. - A file changed on two nodes becomes one file with both versions, the newer first, then a line naming each node and time, then the older; it is carried, and the operator is warned through `seat:operator-channel.notify` (warning, keyed per file, no path in the words; cleared when the file next changes without a conflict). - A removal is a marker value (never a deleted key, kept 30 days): an unchanged file elsewhere moves into the copies, a changed one is kept and carried again. A missing folder, a key the state lost, and a deleted key remove nothing. - Homes of the two kinds (the operator's own, an agent account's own) never take each other's memory; a confined agent in a home not its own carries nothing. - Not carried, and listed with why: symbolic links, files over 128 KiB, files with a key block, a signed token, a known token prefix or a password in an address (narrower than the SDK's check on purpose: notes name commit hashes). - `claude_code_memory`: read-only, fingerprints never text. - Manifest: state `memory`, tool `claude_code_memory`. README: the new section and the NATS row. - No tool writes a memory file or the state (issue 340's boundary). ## Tested - New `memory_test.go` (temporary homes, a state in memory with the bus's watch semantics, never the live folders): carried unchanged and only once; only memory folders; exclusions listed; the two home kinds apart; conflict kept twice newer first, one warning, cleared on tidy, both versions in the copies; removal moves the unchanged file into the copies; a change outlives a removal in either order; missing folder / lost key / deleted key remove nothing; first carrying keeps both indexes and each node's own files; nothing written through a symbolic link; manifest declares the state. - On main the test file does not build (`MemoryCarrier` undefined); with the fix the package passes, also with `-race -count=3` on the memory tests. - `MESH_GATE=<controller built from mesh-controller main> MESH_CHECK_MODULES=claude-code sh merge-check.sh` passes. ## Acceptance A memory file written on g14 appears unchanged on shanks within a minute (`claude_code_memory` on shanks lists it with g14's fingerprint), and a file changed on both before either carried it is kept twice in one file, newer first, with one warning on the operator's channel. ## Rollout note On the first look after delivery, every node carries its existing memory; where two nodes hold different versions of one file (MEMORY.md above all), both are kept in it and one warning per such file is sent. Nothing is lost: each node's prior version is also in its `memory-copies/`. Left open, tracked: a value written straight into the state by a holder of a tool runner's credential is not told apart (issue 638).
mesh-admin added 1 commit 2026-10-13 13:58:16 +00:00
claude-code: carry each project's memory folder between nodes (issue 367, ADR 0343)
mesh/merge-gate head 6aa30eae66be merged into trunk a84d0094a0a3 — pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes …
mesh/repo-check head 6aa30eae66be merged into trunk a84d0094a0a3 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery superseded: a newer head of the same pull request
6aa30eae66
A session started on another node began without what the sessions elsewhere had
learned, because Claude Code keeps project memory on one node. The module's own
process now carries the memory folders through its kept memory state: a change
is written elsewhere only over the version it replaced, a file changed on two
nodes keeps both (newer first) with a warning, and every replaced or removed
version is kept. No tool writes memory.
Author
Contributor

Delivery novox/mesh-catalog@305601d25ef4 — delivered since 2026-10-13T14:30:36Z

Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step

  • tier 0: claude-code
  • ace: receives claude-code
  • g14: receives claude-code
  • novox: receives claude-code
  • shanks: receives claude-code
  • claude-code keeps data (ADR 0233): what it holds is backed up before it moves
  • grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine

Machines

  • claude-code on ace: passed — healthy 1 times over 45s
  • claude-code on the rest running it: sent

Transitions

  • 2026-10-13 14:20 (new) → proposed (announced): novox/mesh-catalog#276's head announced
  • 2026-10-13 14:22 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
  • 2026-10-13 14:22 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
  • 2026-10-13 14:27 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
  • 2026-10-13 14:27 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
  • 2026-10-13 14:30 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says

The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.

<!-- mesh-delivery:view --> **Delivery** `novox/mesh-catalog@305601d25ef4` — **delivered** since 2026-10-13T14:30:36Z **Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step - tier 0: claude-code - ace: receives claude-code - g14: receives claude-code - novox: receives claude-code - shanks: receives claude-code - claude-code keeps data (ADR 0233): what it holds is backed up before it moves - grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine **Machines** - claude-code on ace: passed — healthy 1 times over 45s - claude-code on the rest running it: sent **Transitions** - 2026-10-13 14:20 (new) → proposed (announced): novox/mesh-catalog#276's head announced - 2026-10-13 14:22 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit - 2026-10-13 14:22 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail - 2026-10-13 14:27 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move - 2026-10-13 14:27 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit - 2026-10-13 14:30 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Author
Contributor

Merge check at 6aa30eae
Judged: head 6aa30eae66be merged into trunk a84d0094a0a3; read beside it: mesh-controller 6f1a05366c1d, mesh-controller-main 6f1a05366c1d, mesh-host 0e92ef3d1b21, mesh-tools 954e1a53eef9

  • mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
  • mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)

Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step

  • tier 0: claude-code
  • ace: receives claude-code
  • g14: receives claude-code
  • novox: receives claude-code
  • shanks: receives claude-code
  • claude-code keeps data (ADR 0233): what it holds is backed up before it moves
  • grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
  • read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/memory_test.go

Run by the build seat on ace as build-1791899928332854741 (builds --log build-1791899928332854741).

--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 48 tool(s), keeps state servers, holdings, config, proposals, questions, memory, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T13:42:41Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T13:42:41Z

modules the change moves:
  - claude-code (changed)

machines:
  - a machine (f35): composes
  - holds mesh-dns-resolver (idt): composes
  - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
  - a machine (yvbpsm): composes

a merge would rebuild 1 module(s) in 1 tier(s): claude-code

notes:
  - modules/claude-code/README.md and modules/claude-code/cmd/claude-code/memory_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok  	git.novox.be/novox/mesh-catalog/checks/words	0.003s
words: 12 retired words and 70 daemons' lines, none in the 46498 strings of 717 files
ok  	git.novox.be/novox/mesh-catalog/checks/agenthome	0.011s
agent-home: no archive and no file written into under the agent's home
ok  	git.novox.be/novox/mesh-catalog/checks/secrets	0.015s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok  	git.novox.be/novox/mesh-catalog/checks/paths	0.005s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok  	git.novox.be/novox/mesh-catalog/checks/deps	0.239s
testing modules/claude-code
ok  	claude-code/cmd/claude-code	3.818s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `6aa30eae` Judged: head `6aa30eae66be` merged into trunk `a84d0094a0a3`; read beside it: mesh-controller `6f1a05366c1d`, mesh-controller-main `6f1a05366c1d`, mesh-host `0e92ef3d1b21`, mesh-tools `954e1a53eef9` - `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code) - `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript) **Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step - tier 0: claude-code - ace: receives claude-code - g14: receives claude-code - novox: receives claude-code - shanks: receives claude-code - claude-code keeps data (ADR 0233): what it holds is backed up before it moves - grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine - read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/memory_test.go Run by the build seat on ace as `build-1791899928332854741` (`builds --log build-1791899928332854741`). ``` --- the gate: claude-code note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here claude-code: ok, 48 tool(s), keeps state servers, holdings, config, proposals, questions, memory, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339) long-running resources without health: 0 files asking for a setting without saying whether it is trusted: 1 1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T13:42:41Z says they run composing every machine as the mesh is the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued composing every machine with the change the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose) judged by controller development build against the facts of 2026-10-13T13:42:41Z modules the change moves: - claude-code (changed) machines: - a machine (f35): composes - holds mesh-dns-resolver (idt): composes - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes - a machine (yvbpsm): composes a merge would rebuild 1 module(s) in 1 tier(s): claude-code notes: - modules/claude-code/README.md and modules/claude-code/cmd/claude-code/memory_test.go read by no module's build, and rebuild nothing (issue 280) --- its merge-check.sh (go toolchain) ok git.novox.be/novox/mesh-catalog/checks/words 0.003s words: 12 retired words and 70 daemons' lines, none in the 46498 strings of 717 files ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.011s agent-home: no archive and no file written into under the agent's home ok git.novox.be/novox/mesh-catalog/checks/secrets 0.015s secrets: no setting can replace a vault secret in a mergeable file (1 known, nested) ok git.novox.be/novox/mesh-catalog/checks/paths 0.005s paths: no module names another's state on its machine (3 known, listed to go) long-running resources without health: 66, as kept ok git.novox.be/novox/mesh-catalog/checks/deps 0.239s testing modules/claude-code ok claude-code/cmd/claude-code 3.818s --- its merge-check-typescript.sh (typescript toolchain) ```
jschoubben added 1 commit 2026-10-13 14:01:34 +00:00
claude-code: say a memory conflict in the operator's plain words, and skip a look the state does not answer (issue 367)
mesh/merge-gate head 0096beeff999 merged into trunk a84d0094a0a3 — pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes …
mesh/repo-check head 0096beeff999 merged into trunk a84d0094a0a3 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery superseded: a newer head of the same pull request
0096beeff9
The warning now opens with what the operator is needed for and names no path,
agent or session (ADR 0253's plain rule); clause references follow ADR 0343 as
reviewed.
jschoubben added 1 commit 2026-10-13 14:04:53 +00:00
claude-code: keep two memory versions too large for one file in two, and say a text that cannot fit one value (issue 367)
mesh/merge-gate head 910bc58fe01d merged into trunk a84d0094a0a3 — pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes …
mesh/repo-check head 910bc58fe01d merged into trunk a84d0094a0a3 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery superseded: a newer head of the same pull request
910bc58fe0
A merged file could exceed the state's 256 KiB value; the newer now stays in the
file and the older is written beside it, each carried. A node that was away takes
the newest version when its own file holds no uncarried change (tested).
Author
Contributor

Merge check at 0096beef
Judged: head 0096beeff999 merged into trunk a84d0094a0a3; read beside it: mesh-controller 6f1a05366c1d, mesh-controller-main 6f1a05366c1d, mesh-host 0e92ef3d1b21, mesh-tools 954e1a53eef9

  • mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
  • mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)

Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step

  • tier 0: claude-code
  • ace: receives claude-code
  • g14: receives claude-code
  • novox: receives claude-code
  • shanks: receives claude-code
  • claude-code keeps data (ADR 0233): what it holds is backed up before it moves
  • grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
  • read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/memory_test.go

Run by the build seat on novox as build-1791900110428544412 (builds --log build-1791900110428544412).

--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 48 tool(s), keeps state servers, holdings, config, proposals, questions, memory, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T13:42:41Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T13:42:41Z

modules the change moves:
  - claude-code (changed)

machines:
  - a machine (f35): composes
  - holds mesh-dns-resolver (idt): composes
  - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
  - a machine (yvbpsm): composes

a merge would rebuild 1 module(s) in 1 tier(s): claude-code

notes:
  - modules/claude-code/README.md and modules/claude-code/cmd/claude-code/memory_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok  	git.novox.be/novox/mesh-catalog/checks/words	0.012s
words: 12 retired words and 70 daemons' lines, none in the 46501 strings of 717 files
ok  	git.novox.be/novox/mesh-catalog/checks/agenthome	0.013s
agent-home: no archive and no file written into under the agent's home
ok  	git.novox.be/novox/mesh-catalog/checks/secrets	0.014s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok  	git.novox.be/novox/mesh-catalog/checks/paths	0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok  	git.novox.be/novox/mesh-catalog/checks/deps	0.270s
testing modules/claude-code
ok  	claude-code/cmd/claude-code	6.345s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `0096beef` Judged: head `0096beeff999` merged into trunk `a84d0094a0a3`; read beside it: mesh-controller `6f1a05366c1d`, mesh-controller-main `6f1a05366c1d`, mesh-host `0e92ef3d1b21`, mesh-tools `954e1a53eef9` - `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code) - `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript) **Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step - tier 0: claude-code - ace: receives claude-code - g14: receives claude-code - novox: receives claude-code - shanks: receives claude-code - claude-code keeps data (ADR 0233): what it holds is backed up before it moves - grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine - read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/memory_test.go Run by the build seat on novox as `build-1791900110428544412` (`builds --log build-1791900110428544412`). ``` --- the gate: claude-code note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here claude-code: ok, 48 tool(s), keeps state servers, holdings, config, proposals, questions, memory, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339) long-running resources without health: 0 files asking for a setting without saying whether it is trusted: 1 1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T13:42:41Z says they run composing every machine as the mesh is the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued composing every machine with the change the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose) judged by controller development build against the facts of 2026-10-13T13:42:41Z modules the change moves: - claude-code (changed) machines: - a machine (f35): composes - holds mesh-dns-resolver (idt): composes - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes - a machine (yvbpsm): composes a merge would rebuild 1 module(s) in 1 tier(s): claude-code notes: - modules/claude-code/README.md and modules/claude-code/cmd/claude-code/memory_test.go read by no module's build, and rebuild nothing (issue 280) --- its merge-check.sh (go toolchain) ok git.novox.be/novox/mesh-catalog/checks/words 0.012s words: 12 retired words and 70 daemons' lines, none in the 46501 strings of 717 files ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s agent-home: no archive and no file written into under the agent's home ok git.novox.be/novox/mesh-catalog/checks/secrets 0.014s secrets: no setting can replace a vault secret in a mergeable file (1 known, nested) ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s paths: no module names another's state on its machine (3 known, listed to go) long-running resources without health: 66, as kept ok git.novox.be/novox/mesh-catalog/checks/deps 0.270s testing modules/claude-code ok claude-code/cmd/claude-code 6.345s --- its merge-check-typescript.sh (typescript toolchain) ```
Author
Contributor

Two commits since the description, after the knowledge base's review of ADR 0343:

  • 0096bee: the conflict warning follows ADR 0253's plain rule (explanation opens "Needs you: ...", no path, file name, agent or session; headline at most 60 characters), tested; a look the state does not answer carries nothing and says so (no 312 failed reads every 10 s while the bus is away).
  • 910bc58: two versions too large to share one file (the state's value caps at 256 KiB) are kept in two: the newer in the file, the older beside it as <name>.older-from-<node><ext>, each carried, one warning; a text that would not fit one value once encoded is not carried and is listed. New tests: a node that was away while a file changed twice takes the newest version and keeps the replaced one; two large versions end as two files on both nodes.

merge-check.sh with MESH_CHECK_MODULES=claude-code and a controller from main passes locally; go test -race -count=2 of the package passes.

Two commits since the description, after the knowledge base's review of ADR 0343: - 0096bee: the conflict warning follows ADR 0253's plain rule (explanation opens "Needs you: ...", no path, file name, agent or session; headline at most 60 characters), tested; a look the state does not answer carries nothing and says so (no 312 failed reads every 10 s while the bus is away). - 910bc58: two versions too large to share one file (the state's value caps at 256 KiB) are kept in two: the newer in the file, the older beside it as `<name>.older-from-<node><ext>`, each carried, one warning; a text that would not fit one value once encoded is not carried and is listed. New tests: a node that was away while a file changed twice takes the newest version and keeps the replaced one; two large versions end as two files on both nodes. merge-check.sh with MESH_CHECK_MODULES=claude-code and a controller from main passes locally; `go test -race -count=2` of the package passes.
Author
Contributor

Merge check at 910bc58f
Judged: head 910bc58fe01d merged into trunk a84d0094a0a3; read beside it: mesh-controller 6f1a05366c1d, mesh-controller-main 6f1a05366c1d, mesh-host 0e92ef3d1b21, mesh-tools 954e1a53eef9

  • mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
  • mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)

Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step

  • tier 0: claude-code
  • ace: receives claude-code
  • g14: receives claude-code
  • novox: receives claude-code
  • shanks: receives claude-code
  • claude-code keeps data (ADR 0233): what it holds is backed up before it moves
  • grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
  • read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/memory_test.go

Run by the build seat on shanks as build-1791900325049576514 (builds --log build-1791900325049576514).

--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 48 tool(s), keeps state servers, holdings, config, proposals, questions, memory, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:02:41Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T14:02:41Z

modules the change moves:
  - claude-code (changed)

machines:
  - a machine (f35): composes
  - holds mesh-dns-resolver (idt): composes
  - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
  - a machine (yvbpsm): composes

a merge would rebuild 1 module(s) in 1 tier(s): claude-code

notes:
  - modules/claude-code/README.md and modules/claude-code/cmd/claude-code/memory_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok  	git.novox.be/novox/mesh-catalog/checks/words	0.003s
words: 12 retired words and 70 daemons' lines, none in the 46508 strings of 717 files
ok  	git.novox.be/novox/mesh-catalog/checks/agenthome	0.012s
agent-home: no archive and no file written into under the agent's home
ok  	git.novox.be/novox/mesh-catalog/checks/secrets	0.012s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok  	git.novox.be/novox/mesh-catalog/checks/paths	0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok  	git.novox.be/novox/mesh-catalog/checks/deps	0.219s
testing modules/claude-code
ok  	claude-code/cmd/claude-code	6.112s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `910bc58f` Judged: head `910bc58fe01d` merged into trunk `a84d0094a0a3`; read beside it: mesh-controller `6f1a05366c1d`, mesh-controller-main `6f1a05366c1d`, mesh-host `0e92ef3d1b21`, mesh-tools `954e1a53eef9` - `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code) - `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript) **Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step - tier 0: claude-code - ace: receives claude-code - g14: receives claude-code - novox: receives claude-code - shanks: receives claude-code - claude-code keeps data (ADR 0233): what it holds is backed up before it moves - grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine - read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/memory_test.go Run by the build seat on shanks as `build-1791900325049576514` (`builds --log build-1791900325049576514`). ``` --- the gate: claude-code note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here claude-code: ok, 48 tool(s), keeps state servers, holdings, config, proposals, questions, memory, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339) long-running resources without health: 0 files asking for a setting without saying whether it is trusted: 1 1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:02:41Z says they run composing every machine as the mesh is the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued composing every machine with the change the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose) judged by controller development build against the facts of 2026-10-13T14:02:41Z modules the change moves: - claude-code (changed) machines: - a machine (f35): composes - holds mesh-dns-resolver (idt): composes - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes - a machine (yvbpsm): composes a merge would rebuild 1 module(s) in 1 tier(s): claude-code notes: - modules/claude-code/README.md and modules/claude-code/cmd/claude-code/memory_test.go read by no module's build, and rebuild nothing (issue 280) --- its merge-check.sh (go toolchain) ok git.novox.be/novox/mesh-catalog/checks/words 0.003s words: 12 retired words and 70 daemons' lines, none in the 46508 strings of 717 files ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.012s agent-home: no archive and no file written into under the agent's home ok git.novox.be/novox/mesh-catalog/checks/secrets 0.012s secrets: no setting can replace a vault secret in a mergeable file (1 known, nested) ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s paths: no module names another's state on its machine (3 known, listed to go) long-running resources without health: 66, as kept ok git.novox.be/novox/mesh-catalog/checks/deps 0.219s testing modules/claude-code ok claude-code/cmd/claude-code 6.112s --- its merge-check-typescript.sh (typescript toolchain) ```
mesh-admin left a comment
Author
Contributor

Verdict: request changes at 910bc58fe0

Independent review for issue 367 against ADR 0343 (version 14). Tests pass niced with -race (ok claude-code/cmd/claude-code 7.0s), and the clause-by-clause shape matches the record. Four faults were confirmed with probe tests in a throwaway clone (not pushed); two touch data safety and one touches ADR 0201 §13.

  1. Path traversal from the state writes outside ~/.claude/projects/*/memory/ (blocking). Take (memory.go:714, 728) only checks that the key equals MemoryKey(kind, v.Project, v.Path), but the key is just base64 of project/path, so a writer of the state chooses both. memoryFolder/readLocal/writeLocal/removeLocal/keep (memory.go:238-396) join v.Project unchecked; inside() guards only path against its own folder. Probe: a value with project ../../../escaped wrote <root>/shanks/escaped/memory/x.md on the other node. On a confined node it is worse: Files.through (homefs.go:73) routes any path outside the agent's home to the plain filesystem as the runtime's (operator's) account, so a value in the agent half can write into the operator's home, which breaks ADR 0343 §4. keep() (memory.go:343) also writes copies outside memory-copies/ the same way. Fix: refuse a project that is not one directory entry (entryName, homefs.go) and a path that is absolute, has a .. or empty element, or a NUL; apply it in Take, apply and lookAt before any filesystem call; add a test.

  2. A read failure is carried as a removal (blocking, data safety). scan (memory.go:263-293) returns nil from the WalkDir callback on a walk error, a Lstat error or a ReadFile error, so that file is in neither files nor not; Look (memory.go:560-567) then treats it as gone in an existing folder and removedHere publishes a removal marker; every other node moves its copy into memory-copies/. Probe: chmod 000 on one file on g14 removed it from shanks. Through the helper (homefs.go:222-236) a failed readdir of the memory folder after a successful lstat calls fn(root, d, err), which full == dir swallows, so a helper hiccup on the confined node removes every carried file of that project on every agent-kind node. Fix: record any error under a folder and judge no removal in that project on that look (or put the file in not).

  3. Text never screened goes into the state (blocking, ADR 0343 §2 and ADR 0201 §13). apply reads the local file with readLocal and carries it without memoryProblem: the removal branch (memory.go:768-773) puts the local text back, and the conflict branch (memory.go:798) goes to conflictBeside (memory.go:652-668), which puts the newer side unscreened when the merge is refused for a secret or size. Probes: a local file holding a ghp_... token (listed not carried) was put into the state verbatim (a) after another node removed the file and (b) after another node changed it. Same path lets a file over 128 KiB through. Fix: screen every text before every put/Update (one place in put and in both conflict writers) and keep a file that fails the screen out of the state, listed.

  4. A conflict whose local write fails is merged again on every look (should fix). conflict (memory.go:627-634) updates the state before writeLocal; if that fails (for example keep cannot write memory-copies/), the record is not updated and the next look merges the local text into the already-merged value again. Probe: five looks gave a state value with five merge lines (1796 bytes), carried to every node each time. No warning is sent then (warn comes after the write), so the operator does not see it. Fix: write locally (and keep) first, or record the merged digest as carried for that key when the state write succeeded, and say the failure loudly.

Smaller points, not blocking:

  • memory.go:308-332 vs 383: apply decides on one read and writeLocal re-reads and renames; a change by the program between them is kept in the copies but no conflict is raised. Low.
  • A file that is not valid UTF-8 is carried with the digest of the raw bytes but text mangled by JSON (memory.go:592), so every other node refuses it at memory.go:744 and it is never listed as not carried (§2 says every file not carried is listed).
  • apply's removal case (memory.go:760): a node whose local file is still its last-carried version but not the removal's base carries the file back; conservative, but §9 reads as moving it. Say which is meant.
  • Conflict warnings: one per conflict, keyed by noticeKey (memory.go:433-472), and the merged file does not re-trigger on the other nodes (marker check memory.go:790). Correct, apart from point 4.
  • Home kinds and novox: HomeKind (memory.go:129-138) and the prefix and kind checks in Take are correct; point 1 is the only way across.
  • The tool answers only fingerprints and no event carries text; Say lines carry project and path, never text. Correct.
  • The manifest declares the memory state with no bucket-wide lifetime, so the earlier knowledge-base remark (do not expire live values) holds.
Verdict: request changes at 910bc58fe01d989097ca21920e9eaa0558e0650e Independent review for issue 367 against ADR 0343 (version 14). Tests pass niced with -race (`ok claude-code/cmd/claude-code 7.0s`), and the clause-by-clause shape matches the record. Four faults were confirmed with probe tests in a throwaway clone (not pushed); two touch data safety and one touches ADR 0201 §13. 1. Path traversal from the state writes outside ~/.claude/projects/*/memory/ (blocking). `Take` (memory.go:714, 728) only checks that the key equals `MemoryKey(kind, v.Project, v.Path)`, but the key is just base64 of `project/path`, so a writer of the state chooses both. `memoryFolder`/`readLocal`/`writeLocal`/`removeLocal`/`keep` (memory.go:238-396) join `v.Project` unchecked; `inside()` guards only `path` against its own folder. Probe: a value with project `../../../escaped` wrote `<root>/shanks/escaped/memory/x.md` on the other node. On a confined node it is worse: `Files.through` (homefs.go:73) routes any path outside the agent's home to the plain filesystem as the runtime's (operator's) account, so a value in the `agent` half can write into the operator's home, which breaks ADR 0343 §4. `keep()` (memory.go:343) also writes copies outside `memory-copies/` the same way. Fix: refuse a project that is not one directory entry (`entryName`, homefs.go) and a path that is absolute, has a `..` or empty element, or a NUL; apply it in `Take`, `apply` and `lookAt` before any filesystem call; add a test. 2. A read failure is carried as a removal (blocking, data safety). `scan` (memory.go:263-293) returns nil from the WalkDir callback on a walk error, a Lstat error or a ReadFile error, so that file is in neither `files` nor `not`; `Look` (memory.go:560-567) then treats it as gone in an existing folder and `removedHere` publishes a removal marker; every other node moves its copy into `memory-copies/`. Probe: chmod 000 on one file on g14 removed it from shanks. Through the helper (homefs.go:222-236) a failed `readdir` of the memory folder after a successful lstat calls fn(root, d, err), which `full == dir` swallows, so a helper hiccup on the confined node removes every carried file of that project on every agent-kind node. Fix: record any error under a folder and judge no removal in that project on that look (or put the file in `not`). 3. Text never screened goes into the state (blocking, ADR 0343 §2 and ADR 0201 §13). `apply` reads the local file with `readLocal` and carries it without `memoryProblem`: the removal branch (memory.go:768-773) puts the local text back, and the conflict branch (memory.go:798) goes to `conflictBeside` (memory.go:652-668), which puts the newer side unscreened when the merge is refused for a secret or size. Probes: a local file holding a `ghp_...` token (listed not carried) was put into the state verbatim (a) after another node removed the file and (b) after another node changed it. Same path lets a file over 128 KiB through. Fix: screen every text before every `put`/`Update` (one place in `put` and in both conflict writers) and keep a file that fails the screen out of the state, listed. 4. A conflict whose local write fails is merged again on every look (should fix). `conflict` (memory.go:627-634) updates the state before `writeLocal`; if that fails (for example `keep` cannot write `memory-copies/`), the record is not updated and the next look merges the local text into the already-merged value again. Probe: five looks gave a state value with five merge lines (1796 bytes), carried to every node each time. No warning is sent then (warn comes after the write), so the operator does not see it. Fix: write locally (and keep) first, or record the merged digest as carried for that key when the state write succeeded, and say the failure loudly. Smaller points, not blocking: - memory.go:308-332 vs 383: `apply` decides on one read and `writeLocal` re-reads and renames; a change by the program between them is kept in the copies but no conflict is raised. Low. - A file that is not valid UTF-8 is carried with the digest of the raw bytes but text mangled by JSON (memory.go:592), so every other node refuses it at memory.go:744 and it is never listed as not carried (§2 says every file not carried is listed). - apply's removal case (memory.go:760): a node whose local file is still its last-carried version but not the removal's base carries the file back; conservative, but §9 reads as moving it. Say which is meant. - Conflict warnings: one per conflict, keyed by `noticeKey` (memory.go:433-472), and the merged file does not re-trigger on the other nodes (marker check memory.go:790). Correct, apart from point 4. - Home kinds and novox: `HomeKind` (memory.go:129-138) and the prefix and kind checks in `Take` are correct; point 1 is the only way across. - The tool answers only fingerprints and no event carries text; `Say` lines carry project and path, never text. Correct. - The manifest declares the `memory` state with no bucket-wide lifetime, so the earlier knowledge-base remark (do not expire live values) holds.
jschoubben added 1 commit 2026-10-13 14:20:04 +00:00
claude-code: memory carrying never leaves a memory folder, never takes the unreadable for gone, and screens every write (issue 367, review of #276)
mesh/merge-gate head 305601d25ef4 merged into trunk a84d0094a0a3 — pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes …
mesh/repo-check head 305601d25ef4 merged into trunk a84d0094a0a3 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
305601d25e
A value in the state could name a project such as ../../.. and be written outside
the memory folder; a file or folder that could not be read was carried as a
removal; text kept over a removal or merged in a conflict reached the state
unscreened; and a conflict whose file could not be written was merged into the
state again on every look. Names are judged before any file is touched, what
cannot be read is kept and listed, every state write is screened, a conflict is
written to the file before the state and warns once when it cannot be, a file
changed between reading and writing is not written over, non-UTF-8 text is not
carried, and a node that was away takes a removal it holds no change against
(ADR 0343 §9, as clarified).
Author
Contributor

Merge check at 305601d2
Judged: head 305601d25ef4 merged into trunk a84d0094a0a3; read beside it: mesh-controller 6f1a05366c1d, mesh-controller-main 6f1a05366c1d, mesh-host 0e92ef3d1b21, mesh-tools 954e1a53eef9

  • mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code)
  • mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)

Delivery plan — builds claude-code → ace, g14, novox, shanks; no bus step

  • tier 0: claude-code
  • ace: receives claude-code
  • g14: receives claude-code
  • novox: receives claude-code
  • shanks: receives claude-code
  • claude-code keeps data (ADR 0233): what it holds is backed up before it moves
  • grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine
  • read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/memory_test.go

Run by the build seat on ace as build-1791901209019351906 (builds --log build-1791901209019351906).

--- the gate: claude-code
note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here
claude-code: ok, 48 tool(s), keeps state servers, holdings, config, proposals, questions, memory, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339)
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:02:41Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T14:02:41Z

modules the change moves:
  - claude-code (changed)

machines:
  - a machine (f35): composes
  - holds mesh-dns-resolver (idt): composes
  - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
  - a machine (yvbpsm): composes

a merge would rebuild 1 module(s) in 1 tier(s): claude-code

notes:
  - modules/claude-code/README.md and modules/claude-code/cmd/claude-code/memory_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok  	git.novox.be/novox/mesh-catalog/checks/words	0.003s
words: 12 retired words and 70 daemons' lines, none in the 46565 strings of 717 files
ok  	git.novox.be/novox/mesh-catalog/checks/agenthome	0.012s
agent-home: no archive and no file written into under the agent's home
ok  	git.novox.be/novox/mesh-catalog/checks/secrets	0.012s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok  	git.novox.be/novox/mesh-catalog/checks/paths	0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok  	git.novox.be/novox/mesh-catalog/checks/deps	0.231s
testing modules/claude-code
ok  	claude-code/cmd/claude-code	5.846s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `305601d2` Judged: head `305601d25ef4` merged into trunk `a84d0094a0a3`; read beside it: mesh-controller `6f1a05366c1d`, mesh-controller-main `6f1a05366c1d`, mesh-host `0e92ef3d1b21`, mesh-tools `954e1a53eef9` - `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: claude-code) - `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript) **Delivery plan** — builds claude-code → ace, g14, novox, shanks; no bus step - tier 0: claude-code - ace: receives claude-code - g14: receives claude-code - novox: receives claude-code - shanks: receives claude-code - claude-code keeps data (ADR 0233): what it holds is backed up before it moves - grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before claude-code is judged on its first machine - read by no module's build: modules/claude-code/README.md, modules/claude-code/cmd/claude-code/memory_test.go Run by the build seat on ace as `build-1791901209019351906` (`builds --log build-1791901209019351906`). ``` --- the gate: claude-code note: claude-code uses the seat "operator-channel", which no module declares and the mesh does not define among the manifests given; registration judges it against the whole catalogue, and passing the declaring module's manifest too judges it here claude-code: ok, 48 tool(s), keeps state servers, holdings, config, proposals, questions, memory, reads claude-licence-manager.bindings, keeps agent-home (valuable); WARNING: settings ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339) long-running resources without health: 0 files asking for a setting without saying whether it is trusted: 1 1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T14:02:41Z says they run composing every machine as the mesh is the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued composing every machine with the change the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose) judged by controller development build against the facts of 2026-10-13T14:02:41Z modules the change moves: - claude-code (changed) machines: - a machine (f35): composes - holds mesh-dns-resolver (idt): composes - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes - a machine (yvbpsm): composes a merge would rebuild 1 module(s) in 1 tier(s): claude-code notes: - modules/claude-code/README.md and modules/claude-code/cmd/claude-code/memory_test.go read by no module's build, and rebuild nothing (issue 280) --- its merge-check.sh (go toolchain) ok git.novox.be/novox/mesh-catalog/checks/words 0.003s words: 12 retired words and 70 daemons' lines, none in the 46565 strings of 717 files ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.012s agent-home: no archive and no file written into under the agent's home ok git.novox.be/novox/mesh-catalog/checks/secrets 0.012s secrets: no setting can replace a vault secret in a mergeable file (1 known, nested) ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s paths: no module names another's state on its machine (3 known, listed to go) long-running resources without health: 66, as kept ok git.novox.be/novox/mesh-catalog/checks/deps 0.231s testing modules/claude-code ok claude-code/cmd/claude-code 5.846s --- its merge-check-typescript.sh (typescript toolchain) ```
mesh-admin left a comment
Author
Contributor

Verdict: approve at 305601d25e

Re-review of review 205's findings against ADR 0343 (version 14). I recreated last time's probe tests in a throwaway clone, added three more, and ran them niced with -race against this head. All pass; the four original probes failed at 910bc58f. The module's own tests pass too (ok claude-code/cmd/claude-code 5.7s).

  1. Path traversal is fixed. NameProblem and badName (memory.go:401-424) run in Take, apply, readLocal, writeLocal, removeLocal, keep and write before any file is touched. Probe: values naming ../../../escaped, .., a/b, ../x.md, /abs.md and a//b.md wrote nothing anywhere.
  2. A read failure no longer becomes a removal. scan (memory.go:271-368) marks a file it cannot look at or read as unseen, and leaves a folder it cannot list whole out of folders; Look skips both (memory.go:705). Probes: a chmod-000 file, an unlistable memory folder and an unlistable subfolder on g14 each removed nothing on shanks.
  3. Every state write is screened. write (memory.go:626-654) checks name, key, text and digest. apply's default branch refuses to merge a local text that would not be carried (memory.go:985-990). Probes: a local ghp_... file was not put into the state after a removal or after a change elsewhere, and it stayed on shanks unchanged.
  4. A conflict is no longer merged again and again. The file is written first, then the state by compare-and-set (memory.go:768-778). Probe: five looks with an unwritable memory-copies/ left the state at 311 bytes with no merge line, and sent exactly one warning. Once the folder was writable again, both nodes ended on the same file holding both versions.
  5. Non-UTF-8 text is now listed as not carried and is not carried (probe passes). The redefined §9 (a node still on its last carried version takes the removal) matches §9's wording, and TestARemovalReachesANodeThatWasAway covers it.

Should fix, in this PR or a small follow-up:

  • The new "could not be written" warning (unwritten, memory.go:787-809) is never cleared. No clear is ever sent, so its "resolved" line never fires, and m.failed is never reset. Once the folder is writable again, the operator keeps a stale warning until they dismiss it. Clear it, and reset failed[key], on the next successful write of that file.
  • The same warning's explanation always says "Two versions of one of its files are waiting to be kept together". When it comes from apply's plain write (memory.go:973), no conflict exists and the words are wrong. Word it for both cases.
  • This warning goes beyond ADR 0343 §8, which names only the conflict warning. It is in the spirit of "failure must be loud"; record it in design 36 §12 or in the record.

Not blocking: writeLocal and removeLocal now re-check the digest they read, which narrows the race with the program to the moment between that check and the rename.

Verdict: approve at 305601d25ef451cc8c0c31331092343ee5ba32b9 Re-review of review 205's findings against ADR 0343 (version 14). I recreated last time's probe tests in a throwaway clone, added three more, and ran them niced with -race against this head. All pass; the four original probes failed at 910bc58f. The module's own tests pass too (`ok claude-code/cmd/claude-code 5.7s`). 1. Path traversal is fixed. `NameProblem` and `badName` (memory.go:401-424) run in `Take`, `apply`, `readLocal`, `writeLocal`, `removeLocal`, `keep` and `write` before any file is touched. Probe: values naming `../../../escaped`, `..`, `a/b`, `../x.md`, `/abs.md` and `a//b.md` wrote nothing anywhere. 2. A read failure no longer becomes a removal. `scan` (memory.go:271-368) marks a file it cannot look at or read as unseen, and leaves a folder it cannot list whole out of `folders`; `Look` skips both (memory.go:705). Probes: a chmod-000 file, an unlistable memory folder and an unlistable subfolder on g14 each removed nothing on shanks. 3. Every state write is screened. `write` (memory.go:626-654) checks name, key, text and digest. `apply`'s default branch refuses to merge a local text that would not be carried (memory.go:985-990). Probes: a local `ghp_...` file was not put into the state after a removal or after a change elsewhere, and it stayed on shanks unchanged. 4. A conflict is no longer merged again and again. The file is written first, then the state by compare-and-set (memory.go:768-778). Probe: five looks with an unwritable `memory-copies/` left the state at 311 bytes with no merge line, and sent exactly one warning. Once the folder was writable again, both nodes ended on the same file holding both versions. 5. Non-UTF-8 text is now listed as not carried and is not carried (probe passes). The redefined §9 (a node still on its last carried version takes the removal) matches §9's wording, and `TestARemovalReachesANodeThatWasAway` covers it. Should fix, in this PR or a small follow-up: - The new "could not be written" warning (`unwritten`, memory.go:787-809) is never cleared. No clear is ever sent, so its "resolved" line never fires, and `m.failed` is never reset. Once the folder is writable again, the operator keeps a stale warning until they dismiss it. Clear it, and reset `failed[key]`, on the next successful write of that file. - The same warning's explanation always says "Two versions of one of its files are waiting to be kept together". When it comes from `apply`'s plain write (memory.go:973), no conflict exists and the words are wrong. Word it for both cases. - This warning goes beyond ADR 0343 §8, which names only the conflict warning. It is in the spirit of "failure must be loud"; record it in design 36 §12 or in the record. Not blocking: `writeLocal` and `removeLocal` now re-check the digest they read, which narrows the race with the program to the moment between that check and the rename.
mesh-admin merged commit 305c2ba247 into main 2026-10-13 14:26:59 +00:00
mesh-admin deleted branch fix/367-memory-follows-the-operator 2026-10-13 14:27:00 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-catalog#276