aboutsummaryrefslogtreecommitdiff
path: root/docs/design
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-23 21:00:11 -0400
committerCraig Jennings <c@cjennings.net>2026-06-23 21:00:11 -0400
commit603abc4cb3129be8bd23c89aa69f4f5522d1e5a3 (patch)
tree356c6fc1343e4564d63b91b269aa8f65c1a40236 /docs/design
parentd961c783d18c6178751b338ef1d8dd6a72db9f20 (diff)
downloadrulesets-603abc4cb3129be8bd23c89aa69f4f5522d1e5a3.tar.gz
rulesets-603abc4cb3129be8bd23c89aa69f4f5522d1e5a3.zip
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.
Diffstat (limited to 'docs/design')
-rw-r--r--docs/design/2026-06-22-inbox-zero-capture-hardening.org39
1 files changed, 39 insertions, 0 deletions
diff --git a/docs/design/2026-06-22-inbox-zero-capture-hardening.org b/docs/design/2026-06-22-inbox-zero-capture-hardening.org
new file mode 100644
index 0000000..69acf94
--- /dev/null
+++ b/docs/design/2026-06-22-inbox-zero-capture-hardening.org
@@ -0,0 +1,39 @@
+#+TITLE: inbox-zero Phase D wedges live org-capture sessions on the roam inbox
+
+* The bug
+
+Phase D of =inbox-zero.org= removes claimed items from =~/org/roam/inbox.org= by editing the file on disk (Edit / sed / Write). That collides with any live org-capture session Craig has open against the same file.
+
+org-capture works through an *indirect buffer* cloned from the target file. When the inbox-zero disk write lands and Emacs reverts the main =inbox.org= buffer underneath, the indirect capture buffers are left pointing at stale state and wedge — they can no longer finalize cleanly with =C-c C-c=. The visible symptom is org-capture failing, and one or more orphaned =CAPTURE-*inbox.org= / =CAPTURE-N-inbox.org= buffers piling up as Craig retries.
+
+Hit live on 2026-06-22 during a home-session inbox-zero: I filed three home items, wrote =inbox.org= on disk, and Craig's open capture wedged, leaving two orphaned =CAPTURE-inbox.org= buffers. No data was lost that time (the orphaned buffers held only existing file content, not a freshly-typed item), but that was luck — had he typed an item into the capture before it wedged, finalizing the stale buffer afterward would have written it back against the post-edit file and could have clobbered the routing or a foreign item.
+
+* Why it's worth fixing in the canonical
+
+=inbox-zero.org= is a rulesets-owned synced workflow that runs in every project (startup nudge, wrap-up sub-step, on demand), and the roam inbox is the single shared file all of them edit. Craig edits that same file live in Emacs and captures into it constantly. So the collision window recurs in every project, every session — not a home-only quirk. A local fix in home gets reverted by the next template sync, so the durable fix has to land in rulesets.
+
+* Proposed fix (recommended: guard before the disk write)
+
+Add a pre-edit guard to Phase D, before removing any claimed items:
+
+1. If Emacs is reachable (=emacsclient -e t= succeeds), check for live capture buffers targeting the roam inbox:
+
+ #+begin_src bash
+ emacsclient -e '(mapcar #(quote buffer-name)
+ (seq-filter (lambda (b) (string-match-p "CAPTURE.*inbox" (buffer-name b)))
+ (buffer-list)))' 2>/dev/null
+ #+end_src
+
+2. If any =CAPTURE-*inbox*= buffer exists, *stop before editing* and surface it: "You have a live org-capture session open against the roam inbox — finalize (=C-c C-c=) or abort (=C-c C-k=) it before I route items, otherwise the edit will wedge the capture." Resume Phase D only once it's clear. This mirrors the existing pull-before-edit / surface-and-stop discipline already in Phase D.
+
+3. Independently, when Emacs has =inbox.org= open and *unmodified* (the common case, no live capture), the disk edit is benign — Emacs reverts a clean buffer without complaint. Optionally trigger an explicit =revert-buffer= via emacsclient afterward so the buffer is immediately consistent rather than lazily on next focus.
+
+* Alternative (heavier): do the removal through Emacs when it's running
+
+Instead of editing on disk, when Emacs is reachable, perform the claimed-item removal inside the running daemon (find the buffer, delete the items, save), and fall back to the disk edit only when Emacs isn't running. This keeps Emacs's buffer authoritative and sidesteps the disk/buffer divergence entirely. It's more code and more failure surface for arbitrary item removal, so I'd lean on the guard above unless you want the stronger guarantee.
+
+* Note for whoever builds it
+
+The =emacs.md= rule already covers "don't make Craig restart Emacs; push changes into the running daemon." This is the same principle one layer out: don't edit a file *on disk* that the running daemon is actively editing/capturing into. Worth a line in =emacs.md= too, or at least a cross-reference from inbox-zero Phase D.
+
+Origin: home, 2026-06-22.