This is the record of an abcd-managed repository

abcdBetav0.16.2 · 2026-11-06

Development record

2026-08-06 — iss-186 (bug-hunt loop, round 4): `commitTransition` (`internal/core/capture/workflow.go`) wrote the destination file before removing the source, so a non-ENOENT `os.Remove(src)` failure (EPERM/EROFS/EIO — e.g. an immutable source status dir, or a read-only remount) returned an error after `dst` had already landed, with no rollback. The issue id was then present in both status dirs at once, and `findIssue` rejects any id present in more than one file as `ErrDuplicateIssueID` — so the stranded copy could never again be resolved or wontfixed without a human manually deleting one file; `List`/`Status` also double-counted it, since `scanLedger` does not dedupe. Fixed by rolling the destination back (best-effort `os.Remove(dst)`) whenever the source remove fails for a reason other than "already gone", restoring the pre-call state so a retry is all that is needed once the underlying failure clears; a failed rollback itself is surfaced in the returned error rather than swallowed. A non-ENOENT remove failure has no portable, cross-platform way to trigger deterministically in a test (immutable attributes and read-only remounts are Linux/ext4- and permission-model-specific, and this repo's CI runs macOS and Linux), so a test-only seam (`removeSourceHook`, nil in production) was added, mirroring the existing `beforeOrphanRemoveHook` pattern in `alloc.go`. Repro: `internal/core/capture/workflow_test.go`, `TestTransitionRemoveFailureDoesNotStrandIssueInTwoDirs`, watched failing on pre-fix code for the claimed reason (destination not rolled back) and passing after.

decisiondcn-2608060000000001

2026-08-06 — iss-186 (bug-hunt loop, round 4): commitTransition (internal/core/capture/workflow.go) wrote the destination file before removing the source, so a non-ENOENT os.Remove(src) failure (EPERM/EROFS/EIO — e.g. an immutable source status dir, or a read-only remount) returned an error after dst had already landed, with no rollback. The issue id was then present in both status dirs at once, and findIssue rejects any id present in more than one file as ErrDuplicateIssueID — so the stranded copy could never again be resolved or wontfixed without a human manually deleting one file; List/Status also double-counted it, since scanLedger does not dedupe. Fixed by rolling the destination back (best-effort os.Remove(dst)) whenever the source remove fails for a reason other than "already gone", restoring the pre-call state so a retry is all that is needed once the underlying failure clears; a failed rollback itself is surfaced in the returned error rather than swallowed. A non-ENOENT remove failure has no portable, cross-platform way to trigger deterministically in a test (immutable attributes and read-only remounts are Linux/ext4- and permission-model-specific, and this repo's CI runs macOS and Linux), so a test-only seam (removeSourceHook, nil in production) was added, mirroring the existing beforeOrphanRemoveHook pattern in alloc.go. Repro: internal/core/capture/workflow_test.go, TestTransitionRemoveFailureDoesNotStrandIssueInTwoDirs, watched failing on pre-fix code for the claimed reason (destination not rolled back) and passing after.

Frontmatter

iddcn-2608060000000001
typedecision
date2026-08-06

mentions1