From 603abc4cb3129be8bd23c89aa69f4f5522d1e5a3 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Tue, 23 Jun 2026 21:00:11 -0400 Subject: feat(inbox-zero): guard roam-inbox writes against live org-capture Editing the roam inbox on disk while Emacs has an indirect org-capture buffer cloned from it reverts the base buffer under the capture: the capture can't finalize with C-c C-c, and a freshly-typed item can be lost. inbox-zero Phase D edits that file, which Craig captures into constantly, so the collision recurs every session. I added a capture-guard helper that asks the running daemon whether any CAPTURE buffer's base buffer visits a given file (file-equal-p, so symlinks and path spelling don't matter), exiting non-zero with the names when so. No reachable Emacs or no capture means exit 0, so it never blocks a write that was safe. Phase D calls it before the pull, not only before the remove, because the ff-only pull also rewrites the file on disk and would wedge a capture the same way. On a collision an on-demand run stops and asks Craig to finalize or abort. The wrap-up sub-step skips the roam reconcile without blocking the wrap, since the items are already filed and the next run reclaims them. emacs.md gains the inverse of the reload rule: don't yank a file out from under the daemon's live buffers. --- .ai/workflows/inbox-zero.org | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to '.ai/workflows') diff --git a/.ai/workflows/inbox-zero.org b/.ai/workflows/inbox-zero.org index 4da27bd..5979ec0 100644 --- a/.ai/workflows/inbox-zero.org +++ b/.ai/workflows/inbox-zero.org @@ -71,9 +71,14 @@ Apply =process-inbox.org='s discipline against the project's =todo.org=; don't r The roam inbox lives in a git repo (=~/org/roam=, auto-synced by the =roam-sync= timer). Edit it carefully: -1. *Pull first* (=git -C ~/org/roam pull --ff-only=). If it can't fast-forward (dirty tree, divergence), surface and stop. Don't auto-stash, auto-merge, or force. Resolve before removing items. -2. *Remove only the claimed items.* Never touch foreign or unowned items. -3. *Commit the roam repo as its own commit* (separate from any project wrap commit): =chore(inbox): route tasks to /todo.org=. Push, or leave for the =roam-sync= timer. Surface a blocked push; don't force. +1. *Guard against a live org-capture session* — before any read-modify-write of the roam inbox, run =.ai/scripts/capture-guard "$HOME/org/roam/inbox.org"=. This runs first because the pull in step 2 *also* rewrites the file on disk, and a fast-forward landing underneath a live capture wedges it just as a hand edit would. + - *Exit 0* → no live capture (or no reachable Emacs). Proceed. + - *Exit 1* → an indirect org-capture buffer is cloned from the roam inbox (the script prints the offending buffer name). Editing or fast-forwarding the file underneath it would leave the capture pointing at stale state and unable to finalize with =C-c C-c= (see =emacs.md=). Behavior depends on the caller: + - *On-demand / interactive run* → stop and surface: "You have a live org-capture session open against the roam inbox (==) — finalize it (=C-c C-c=) or abort it (=C-c C-k=) and I'll continue." Re-run the guard and resume once it returns clean. + - *Wrap-up sub-step* → don't block the wrap. Skip the roam reconcile for this run and surface one line: "Skipped roam-inbox reconcile — a live org-capture is open against it; claimed items stay and get caught next run." The items were already filed into =todo.org= in Phase C, so the next inbox-zero run's Phase C status-check drops the duplicates and its Phase D removes them from the roam inbox — the skip self-heals, it doesn't lose anything. +2. *Pull first* (=git -C ~/org/roam pull --ff-only=). If it can't fast-forward (dirty tree, divergence), surface and stop. Don't auto-stash, auto-merge, or force. Resolve before removing items. +3. *Remove only the claimed items.* Never touch foreign or unowned items. +4. *Commit the roam repo as its own commit* (separate from any project wrap commit): =chore(inbox): route tasks to /todo.org=. Push, or leave for the =roam-sync= timer. Surface a blocked push; don't force. * Phase E — Surface -- cgit v1.2.3