PR #1106 follow-ups: unhandled exceptions in claim/release/say nf-q2j ← Beads

closed priority 2 task bd-papercuts

Twinned from https://github.com/cfsh/ninefold/issues/1141 by tools/beads/import_github.py's reconcile pass.

Suggestions from ninefold-reviewer's review of #1106

Two low-severity findings, same class, neither blocking merge: a newly-added verb crashes with a raw Python traceback instead of a clean CLI error, on a rare precondition failure dev.py's established except PlumbingFailed/except ContractBroken convention does not cover.

  1. _bd()'s resolve_beads_workspace() call can raise sessions.ReapRefused, uncaught. tools/nflib/devcli.py's _bd() helper (used by claim/release/say) calls resolve_beads_workspace(Git(cwd=ROOT, runner=seam)), which raises sessions.ReapRefused (not PlumbingFailed) if git worktree list --porcelain fails or lists no working tree (tools/nflib/sessions.py:459-465,490-491). devcli.main()'s dispatcher only catches PlumbingFailed/ContractBroken (devcli.py:2242-2248). Every other consumer of sessions.ReapRefused in this codebase catches it explicitly (merge_pr.py:1108,1505,1554, session_state.py:362, wrap_up.py:322,511) - this is the one call site of that pattern that does not. Fix: add except sessions.ReapRefused to main() alongside the existing handlers, same message shape.
  1. dev.py say's --body-file read has no error handling. verb_say (devcli.py:2141-2161) passes args.body_file straight into gh.comment_file()/bd.comment(), both doing an unguarded Path(body_file).read_text(...). A typo'd path crashes with a raw traceback instead of the clean dev.py say: <error> a bad path should produce. verb_pr reads the same --body-file/-F flag and wraps it in try/except OSError (devcli.py:1220-1228) - the established pattern for this exact flag in this exact file; verb_say does not reuse it.

Both are worth fixing in one pass, since they are the same 'new verb code does not reuse this file's established exception-handling convention' class.

Dependencies

None.

Comments

jcantsp-bot · 2026-08-20T14:38:40Z

Both findings fixed in tools/nflib/devcli.py:

  1. main()'s dispatcher now also catches sessions.ReapRefused (raised by _bd()'s resolve_beads_workspace() call on a git worktree list failure), alongside the existing PlumbingFailed/ContractBroken handlers — same f"dev.py: {exc}" message shape and exit code 2 as every other catch site for this exception (merge_pr.py, session_state.py, wrap_up.py).
  2. verb_say now wraps its body in try/except OSError, reusing verb_pr's exact --body-file convention (devcli.py's established pattern) — a typo'd path now prints dev.py say: --body-file <errno message> and returns 2 instead of a raw traceback.

Added regression tests in devcli_test.py's ClaimReleaseSayVerbTest: test_claim_wraps_reap_refused_from_the_bd_seam (worktree-list failure → clean exit 2, no traceback) and test_say_with_a_missing_body_file_fails_cleanly.

Add a comment