Development record
repo-local load-modify-write sites remain unguarded after the iss-101/102 class fix: intent transitions (review.go, lifecycle.go — strongest candidate), ahoy config.json stepConfigValues, gitignore/marker block rewrites, and the CHANGELOG release path all do load-mutate-write without a lock; they lack the home-global cross-worktree exposure the fixed sites had, so risk is same-worktree concurrency only
issueresolvedminoriss-127
repo-local load-modify-write sites remain unguarded after the iss-101/102 class fix: intent transitions (review.go, lifecycle.go — strongest candidate), ahoy config.json stepConfigValues, gitignore/marker block rewrites, and the CHANGELOG release path all do load-mutate-write without a lock; they lack the home-global cross-worktree exposure the fixed sites had, so risk is same-worktree concurrency only
Grounds
- pursued: two abcd runs in one working tree rewriting the same file keep both changes, or the later cut is refused as a release in flight; a concurrent writer's change missing after a race test (rewritelock_test.go, changeloglock_test.go) would show it wrong
Frontmatter
| id | iss-127 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-127-repo-local-lmw-sites-unguarded.md
open on GitHub ↗ · commit history ↗
mentions3
- iss-101registerRepo does an unlocked load-modify-write of ~/.abcd/history/index.json (loadHistoryIndex -> mutate -> writeHistoryIndex, apply.go), and bootstrapHistory is check-then-act. WriteFileAtomic prevents torn files but not lost updates (last rename wins), so two concurrent 'abcd ahoy install' runs from different worktrees can drop a repo registration or clobber the registry with an empty Repos list, erasing lineage/supersedes links set on explicit user confirmation. Deferred from the multi-agent bug hunt (was B30): a correct fix needs an inter-process O_EXCL/flock lock in ~/.abcd/history held across the load-mutate-write, with a re-load inside the lock and bootstrapHistory creating index.json with O_EXCL. The registerRepo sequence spans an interactive prompter.Confirm (re-founding lineage), so the lock must NOT be held across that prompt (risk of blocking other processes on user input). Low severity, self-heals on next install. Needs a dedicated change.
- iss-2608301306587401Link and Plan do unlocked read-modify-writes on records RecordGrounds now locks
- iss-2609012039102703GHSA-mchq-gm34-3j34 (CWE-754, advisory severity low): `ahoy install` rebuilds an unparseable `.abcd/config.json` that `stepConfigValues` refused to touch. `internal/core/ahoy/apply.go:stepVersionStamp` discards `readConfig`'s error, treats the nil map as empty and republishes a meta-only file through `writeConfig`; `detect.go:detectVersion` discards the same error, so a malformed file raises `install_meta.missing`, which `--yes` arms. `stepConfigValues` refuses correctly (`loadPersistedInstallConfig` ok=false) but silently, so the run ends "partial" with `config.*_missing` remaining and no note naming the malformed file, and `stepVisibility` is skipped so the .gitignore fence is never reconciled. Reproduced at v0.7.0 with a merge-conflicted config: `repo.visibility` (the disclosure fence) and `scan.native_secret_scanning` (the remote-apply opt-out) were gone afterwards. The fix must establish that a config that cannot be parsed is never rewritten by any install step, that detection raises one non-resolvable `config.malformed` diagnostic instead of arming the stamp, and that a partial install says why in its notes — the regression test drives a merge-conflicted config through `install --yes` and asserts the file is byte-identical afterwards. Distinct from iss-127 (concurrent config RMW).