Skip to main content

Module m0034_identity_history_backfill

Module m0034_identity_history_backfill 

Source
Expand description

One-time sweep for identity conversation history stranded before ensure_history_link (SPEC_AGENT_IDENTITY_HISTORY_PERSISTENCE_PROTOCOL_2026_08_16.md, shipped in 0007c6b64051e8 / PR #2605) existed to keep it linked into the always-global shared/identities/<id>/<provider>/<subdir>/ tree.

§What went wrong

ensure_history_link already does the hard part — migrate a real, pre-existing directory’s contents into the global target (never clobbering an existing entry) and replace it with a junction/symlink — but it only ever runs at auth.start and at ordinary agent spawn (identity_auth_dirs::link_history_if_isolated, called from identity/resolver/inject.rs). A channel that stopped being spawned into before the fix landed — retired, superseded by a newer dev branch, or simply abandoned — never spawns again, so that call site never re-runs for it. Its real identities/<bundle>/<provider>/<subdir>/ directory is left exactly as it was: live conversation history that ClaudeHistoryAdapter’s per-channel fallback scan can still find and read (so it isn’t lost), but that will never be linked into the global tree, and will never benefit from a fix to the linking logic itself, because nothing ever calls it again for that channel.

Confirmed on a live install: two channels (local-agentx-fix-lan-discovery-tx-fc5809-ec08a24c, local-main-b28b7a-0e5d07ad) born on 2026-08-16 — the exact day the fix shipped — carry four such real, unlinked, non-empty identity history directories, last written 2026-09-03 and never touched since.

§What this does

Walks every identities/ directory found up to 2 levels under <home>/channels/* and <home>/dev/* — covering channels/<slug>/, dev/<branch>/, and the clone-scoped dev/<branch>/<clone_id>/ shape (see identities_roots, and ClaudeHistoryAdapter’s identical bounded traversal, which this mirrors on purpose). For every identities/<bundle_id>/<provider.auth_dir_name>/<history_subdir> path that exists on disk, calls the live ensure_history_link to merge it into <home>/shared/identities/<bundle_id>/<provider.auth_dir_name>/<history_subdir> and replace it with a link — the exact same operation a spawn would have performed, just run once, retroactively, for every channel that will never spawn again.

Deliberately calls the LIVE ensure_history_link rather than freezing a copy (contrast the guidance in this module’s own doc comment about migrations that produce DB rows): that guidance protects rows whose shape is meaningful and time-bound. This migration produces no rows at all — it performs a filesystem merge-and-link operation that ensure_history_link already defines as idempotent and safe (“never deletes or overwrites data”). Freezing a copy would mean a future safety fix to that function (it has already had one real bug fixed under it, per PR #2605’s review) never applies to this migration, on any machine that runs it after that fix ships — the opposite of what freezing is meant to protect against.

Scoped Global: it only ever writes under <home>/shared/, and must run at most once regardless of which channel happens to run it first.

Never invents new structure: only acts where a real identities/<bundle>/<provider>/<subdir> path already exists on disk. Does not create a link (or the target directory) for a bundle/provider combination that was never present, which ensure_history_link would otherwise happily do (it unconditionally create_dir_alls the target). Best-effort per entry, matching link_history_if_isolated’s own philosophy: one bundle’s I/O error (e.g. a genuine name collision ensure_history_link refuses to resolve automatically) is logged and skipped, never aborts the sweep for every other bundle.

Structs§

M0034IdentityHistoryBackfill

Functions§

collect_identities_roots 🔒
identities_roots 🔒
Every identities/ directory up to 2 levels under <home>/channels/ and <home>/dev/. Deliberately mirrors ClaudeHistoryAdapter::scan_isolated_identities_under’s bounded traversal (crates/srv/src/backend/history/claude_adapter.rs) rather than assuming a fixed one-level shape: DataPaths::resolve places a dev instance at dev/<branch>/ when there is no clone id, but at dev/<branch>/<clone_id>/ whenever one is derived — which agentmux_common::runtime_mode::derive_clone_id does for essentially every real dev checkout (anywhere a .git/Cargo.toml/Taskfile.yml is found up the tree), not an edge case. An earlier revision of this function only checked one level under dev/, which silently found nothing for that layout and would have marked the migration applied without ever backfilling clone-scoped dev instances — caught in review (muxreview + Codex, independently, on the same commit) before merge. Depth 2 covers every known layout: channels/<slug>/identities/, dev/<branch>/identities/, and dev/<branch>/<clone_id>/identities/. Kept as its own copy rather than calling the adapter’s function directly: that one is Claude-specific (hardcodes claude/projects) and private to its module; this migration needs the identities/ directory itself, for every provider.
is_safe_path_segment 🔒
Mirrors agentmux_common::data_paths’s private sanitize_path_segment as a boolean check only (that function isn’t pub, and this call site only ever needs to know whether a name discovered on disk is safe to re-join into a path — never the sanitized value itself, since the original bundle_entry path is already known-safe on this filesystem and is what actually gets used). Kept as its own small, stable copy rather than exporting the original: this predicate is simple path hygiene, not the isolation-sensitive logic ensure_history_link and the migration framework’s own “don’t freeze what might change” note are about.