---
phase: 260527-6ah
plan: 01
subsystem: psyche-sync
tags: [sync, idempotency, gh, git-worktree, bugfix]
requires:
  - src/common/sync.rs accept_flow
  - src/common/git.rs run_git_checked
provides:
  - Idempotent accept_flow Step 1 (repo-already-exists tolerated)
  - Authoritative accept_flow Step 4 origin wiring (add-then-set-url)
  - Two #[cfg(unix)] fake_gh regression tests
affects:
  - $OWL psyche-sync-setup retry-after-partial-run path
tech-stack:
  added: []
  patterns:
    - "add-then-unconditional-set-url for authoritative remote wiring under shared worktree config"
    - "case-insensitive idempotency stderr match after ordered scope-fallback check"
key-files:
  created: []
  modified:
    - src/common/sync.rs
decisions:
  - "repo-already-exists is idempotent success, not RepoCreateFailed — checked AFTER the scope-fallback disposition to preserve order"
  - "Step 4 keeps both add and set-url soft-failed (let _ =) per D-14 per-worktree soft-fail posture"
metrics:
  duration: ~2min
  completed: 2026-05-27
---

# Phase 260527-6ah Plan 01: Fix two idempotency bugs in src/common/sync.rs Summary

Made `accept_flow` safely re-runnable after a partial `$OWL psyche-sync-setup` run: repo-already-exists no longer aborts at Step 1, and a stale shared-worktree-config origin is corrected to the canonical URL at Step 4 before push.

## What Was Built

**Bug B (Step 1, repo create).** After the unchanged scope-fallback check (`missing required scopes` / `HTTP 403` → `ScopeFallbackToBrowser`), a new case-insensitive `stderr.to_lowercase().contains("already exists")` check treats a repo-already-exists failure as idempotent success — `accept_flow` falls through to Step 2+ and persists `state=Enabled` instead of returning `RepoCreateFailed`. Disposition order preserved: scope-fallback first, then already-exists, then `RepoCreateFailed` for any other nonzero stderr.

**Bug A (Step 4, origin wiring).** Git worktrees share one config, so once any worktree has an `origin`, `git remote add origin` is a swallowed no-op error and a wrong/stale URL survives → Step 5 `push --all origin` 404s. Replaced the single soft `remote add` with an authoritative add-then-unconditional `remote set-url origin <canonical-url>` per worktree. Both calls stay swallowed with `let _ =` (D-14 per-worktree soft-fail). The sequence is correct whether origin was absent (add succeeds, set-url reasserts) or present-but-stale (add no-ops, set-url corrects).

**Tests.** Two new `#[cfg(unix)]` fake_gh regression tests added after Test 6:
- `accept_flow_repo_exists_is_idempotent` — fake `gh repo create` emits `Name already exists on this account` + exits 1; asserts `accept_flow("testuser")` returns the canonical URL and persists `state=Enabled` with `remote_url` set.
- `accept_flow_corrects_stale_worktree_origin` — seeds one worktree under `tracked_root()/agents/sabermage` with a `.git` file; fake git logs the URL following `origin` for both `add` and `set-url`; asserts the last logged URL equals `https://github.com/SaberMage/spt-agent-storage.git`.

All git invocations go through `crate::common::git::run_git_checked` (D-01); `psyche_sync_setup` exit-code contract and Step 5's hard `push --all` are unchanged.

## Tasks

| Task | Name | Commit | Files |
| ---- | ---- | ------ | ----- |
| 1 | Make accept_flow Step 1 + Step 4 idempotent | b7668dd | src/common/sync.rs |
| 2 | Add two #[cfg(unix)] fake_gh regression tests | e4ceca4 | src/common/sync.rs |

## Verification

- `cargo build` — succeeds, no new warnings (pre-existing dead-code warnings in `echo_commune.rs` / `echo_fire.rs` are unrelated).
- `cargo build --release` — succeeds.
- `cargo test --lib common::sync` — 23 passed, 0 failed (cross-platform sync tests; no regressions).
- The two new tests are `#[cfg(unix)]` and excluded on this Windows dev host (per plan convention + task constraint); they compile-check only here and run on a unix host.

## Deviations from Plan

None — plan executed exactly as written. Both tasks are TDD-tagged, but per the explicit task constraint the two new tests are `#[cfg(unix)]` and cannot execute on this Windows host, so verification used `cargo build` / `cargo build --release` (Task 1) and `cargo build` + `cargo test --lib` (Task 2) as the plan's per-task verify blocks specify, rather than a RED-then-GREEN run of the unix tests.

## Self-Check: PASSED

- src/common/sync.rs — FOUND (modified)
- Commit b7668dd — FOUND
- Commit e4ceca4 — FOUND
