renderTouchOffer had no production caller.
buddy touch reads ticked boxes out of pullRequest.body:
const selected = options.name ? [options.name] : parseTouchSelections(pullRequest.body)Nothing ever wrote any there. So the flow the whole feature page is built around — "tick the checkbox Buddy posts" — could not start, and the touch job sat on pull_request: edited waiting for a tick that no box existed to receive.
The fix
buddy touch <pr> --offer appends the offer, and the generated workflow runs it when a pull request is opened or marked ready. Two moments rather than one: the offer has to exist before there is a box to tick, and edited is when the tick arrives.
- name: Offer finishing touches
if: ${{ github.event.action != 'edited' }}
run: bunx @buddysh/buddy touch ${{ ... }} --offer --verbose
- name: Run finishing touches
if: ${{ github.event.action == 'edited' }}
run: bunx @buddysh/buddy touch ${{ ... }} --verboseThe description, not a comment
The docs' hero shows buddy commented on #128. That would not have worked: the parser reads pullRequest.body, and pull_request: edited — the event the workflow waits on — fires for the description, not for a comment.
Writing the offer where the docs showed it would have produced a box nobody was listening to. The hero block is corrected to match the mechanism.
Idempotence matters here
Guarded by a <!-- buddy:touches --> marker. The job fires on both opened and ready_for_review, so a PR opened as a draft and later marked ready would otherwise collect a second set of boxes that tick independently of the first — and parseTouchSelections dedupes by name, so ticking the second set would look like it did nothing.
An empty offer appends nothing rather than a bare marker, so hasTouchOffer can't report an offer with no boxes under it.
The other half of #1418
touch was one of two jobs that could never start and had nothing to act on if they had. #1418 fixed the first half; this is the second.
test/workflow-reachability.test.ts gains a case for the opening event, so the offer path is covered by the same per-event evaluation.
Tests
2098 → 2100.
Lint ✓ · typecheck ✓ · check:docs ✓ · 0 fail
🤖 Generated with Claude Code