Skip to content

agent-profile — Sync Baseline Amendment

Date: 2026-06-10 Status: Implemented Amends: 2026-05-24-claude-profile-design.md (§5, §8, and parts of §9/§11)

Why

Runtime verification of the v0.1.5 implementation found that the original design's merge baseline — a committed, shared meta.json recording filesystem mtimes — is unsound across machines:

  • mtimes recorded by machine A are meaningless on machine B (clone/pull resets mtimes; clocks and copy times differ), so B misclassified every file on first sync (false-conflict storm);
  • a home edit older than another machine's sync timestamp was classified "unchanged" and silently overwritten (data loss, the §8.1 conflict row never fired);
  • deletions classified as "delete-vs-change" and resurrected.

The §8.3 rationale ("mtime vs baseline survives mtime resets") only holds when the baseline was recorded by the same machine that evaluates it.

What changed

  1. Baseline is per-machine and content-addressed. ~/.agent-profile/state/<profile>/meta.json (version 2) maps each tracked path to the sha256 of its content at this machine's last successful sync, and each tracked ~/.claude.json key to the sha256 of its JSON value. It is never committed. The old committed <checkout>/.agent-profile/ state dir is no longer read, written, or staged; v1 metas parse as "no baseline".

  2. §8.1 decision table reclassified by hash. "Changed since last sync" ⇔ hash ≠ baseline. Mtimes only break conflict ties (newer wins, tie → home). Two new behaviors:

  3. adopt-baseline: both sides hold identical content → record, no copy, no conflict. This makes first contact on a fresh machine a clean no-op and lets independently-converged edits settle silently.
  4. First contact with differing content is a conflict with the loser preserved — never a silent overwrite (the original table's "copy newer → other" row allowed silent loss).

  5. Repo-side artifacts never materialize into ~/.claude/: .conflicts/**, claude.json.partial, and agent-profile.yaml are excluded from the home side of sync/use/init (they remain committed for visibility).

  6. §11 error handling implemented as specified (it previously wasn't): unreachable remote → local merge still runs, warn, exit 0; malformed ~/.claude.json → skip key merge, file merge proceeds, exit 1; unrelated dirty checkout → abort, exit 2; --dry-run prints the plan and writes nothing; the sync lock covers the --pull/--push hook paths; commander usage errors exit 2 (1 stays reserved for "ran with conflicts").

  7. Divergence recovery. Sync/adopt/merge pull (ff-only) before committing; a rejected push triggers fetch + rebase of our own unpushed sync commits + one retry. Remote history is never rewritten; an unresolvable rebase aborts cleanly with exit 2.

  8. In-process composition. use/merge/init invoke the sync engine directly (the previous npx tsx src/... self-exec only worked in a dev checkout). use aborts if the outgoing flush fails. Backups now capture the tracked ~/.claude.json subset as claude.json.partial, and restore re-applies it as a key overlay (never as a literal home file).

  9. merge --strategy newer compares last-commit times (git log -1 --format=%ct -- <path>), which are machine-independent, instead of filesystem mtimes.

  10. config get resolves through all layers (defaults < repo YAML < profile YAML < per-machine overrides) with --explain naming the source layer; dotted config set keys persist under overrides in the machine config (§3 layer 4 as designed).

  11. New command: tui — interactive full-screen viewer over the describe report for every profile in the repo (non-TTY invocations print the reports sequentially).

Regression coverage

tests/integration/regressions.test.ts (R1–R17) pins each of the above: cross-machine conflict preservation, deletion propagation, clean first sync on a second machine, artifact exclusion, status settling, dry-run, offline, dirty-checkout abort, malformed-JSON exit 1, adopt-pull-first, use abort, in-process merge apply, restore key overlay, config resolution, usage exit codes, __preexisting__ backup, and init's initial sync.