fix(agents): recovery must mount a renamed agent's real workspace volume (#1665)#1668
Conversation
…ume (#1665) Rename keeps the agent's volumes under the pre-rename base (Docker can rename neither a volume nor its immutable label), so for a renamed agent the CURRENT name names no volume. `recreate_missing_container` (#1559 soft-delete recovery) f-stringed that name — and `containers.run` CREATES a missing named volume rather than failing, so recovery silently rebuilt the agent on an empty `/home/developer` while its real data (incl. #1169 `data_paths`) sat unreferenced under the old base. Silent, not loud: the agent comes back looking healthy, just empty. Verified against a real DB + daemon, before: recovery mounts `agent-{new}-workspace` -> "<volume does not exist -> docker CREATES it empty>". After: mounts `agent-{old}-workspace` -> the agent's real data. Three sites, one rule — resolve through the ownership row, never the name: - `recreate_missing_container`: the data-loss one, now via the new `_workspace_volume_name` (fail-safe: a DB error falls back to the agent name, the pre-#1665 behavior and correct for every un-renamed agent). - `_read_template_yaml_from_volume`: same trap one level down — it read nothing for a renamed agent, so recovery rebuilt on DEFAULT agent-type/runtime instead of the committed ones. Now recovers `researcher`/`codex` correctly. - `recreate_container_with_updated_config` carried a DEAD `agent_volume_name = f"agent-{agent_name}-workspace"` assignment that read as though it were the mount in use (it isn't — mounts are carried forward from the old container, which is what keeps a renamed agent on its volume across a recreate). Removed and replaced with a note, since a live-looking wrong name is how this bug class spreads. `deploy.py`'s `agent-{version_name}-workspace` is correct by construction (a fresh name, no row, no rename in its past) and is annotated as audited rather than changed. Also stubs `get_volume_base_name` in the #1559 recreate test: it mocks the whole database module, and an unstubbed MagicMock attribute is truthy — it would be f-stringed straight into the volume name (same trap that broke test_fork_to_own in #1666). Related to #1665 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Test results for this head (the net that the stacked base skipped)Forced CI run — green, and it is the real net: run 29569816574 — all 6 Local full unit suite: Merge order stands: #1666 → this. On retarget to |
01d15fb
into
feature/1664-orphan-volume-sweep-rename-safety
|
Superseded by #1666 — folded in, per maintainer's call. GitHub auto-marked this merged the moment Why it was folded rather than kept stacked: this could never merge before #1666 (it needs that PR's #1665 stays open until #1666 reaches |
Related to #1665
The bug
Rename keeps the agent's Docker volumes under the pre-rename base — Docker can rename neither a volume nor its immutable
trinity.agent-namelabel — so for a renamed agent the current name names no volume.recreate_missing_container(#1559 soft-delete recovery) f-stringed that current name.The failure is silent, not loud:
containers.runcreates a missing named volume rather than erroring, so recovery rebuilt the agent on a brand-new empty/home/developerand started it. The agent comes back looking healthy — just empty — while its real data (including #1169data_paths) sits unreferenced under the old base.Reproduced against a real DB + daemon before the fix:
The fix — one rule, three sites
Resolve the volume through the ownership row, never from the agent's name (
_workspace_volume_name; NULL pin ⇒ the name itself ⇒ every un-renamed agent is byte-identical to today). Fail-safe: a DB error falls back to the agent name — the pre-#1665 behavior — rather than blocking a rebuild.recreate_missing_containeragent-{current}-workspace→ empty workspace_read_template_yaml_from_volume{}→ recovery rebuilt on default agent-type/runtimeresearcher/codexrecreate_container_with_updated_configagent_volume_name = f"agent-{agent_name}-workspace"assignment that read as if it were the mount in useThe
deploy.pyversion-name volume is correct by construction (a fresh name, no ownership row yet, no rename in its past) — annotated as audited, not changed.Verification (real DB + real Docker, no mocks)
Tests: 3 new (renamed-agent recovery mounts the real base; the template read resolves too; DB-error fallback), plus 261 passing across the affected suites.
Notes for review
recovertells you tostart, butstart404s when the container is gone #1559 recreate test mocks the wholedatabasemodule, and an unstubbed MagicMock attribute is truthy — it would be f-stringed straight into the volume name. It failed exactly astest_fork_to_owndid in fix(volumes): rename-safe volume identity — sweep, purge, create and recovery (#1664, #1665, #1667) #1666; stubbed explicitly. Worth knowing for any futuredb.*call added to a create/recreate path.get_public_volume_name/get_shared_volume_namename off the live agent name, andrecreate_container_with_updated_configdeliberately drops the old public/shared mounts and re-adds them by current name. So a rename + any recreate silently empties a renamed agent's publish dir and shared-out folder (data orphaned, not deleted — bug: #1581 orphan-volume sweep can destroy a live renamed agent's home volume (rename keeps old volume name+label) #1664's union predicate keeps the sweep off the old base). Unlike the workspace, that one is not a mechanical swap: an agent renamed in the field may already have data underagent-{new}-public, so resolving to the pinned base would orphan that instead. It needs an adopt/migrate decision, not a one-line change.🤖 Generated with Claude Code