fix(approvals): stop fire-and-forget notify() piling up in "needs you" - #60
Merged
ralyodio merged 1 commit intoJul 28, 2026
Merged
Conversation
notify() is fire-and-forget — the CLI posts it and moves on, and nothing ever polls it. Only ask() blocks on a human. POST /api/approvals filed both as `pending`, so every notification parked itself in the dashboard's "needs you" queue permanently: the "N waiting on you" count was wrong, and the only way to clear one was to answer a script that had stopped listening. A notify is done the moment it goes out, so ingest now files it as `sent` with submitted_at set, which puts it in "moshed" history where it belongs. ask() is untouched. resolve() already no-ops on anything but `pending`, so a notify can no longer be flipped by the approve form; the approve page now says so instead of rendering an empty "You replied". Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
notify()is fire-and-forget.src/commands.mjssays so, and it never callspollApproval— it posts, prints the link, and returns. Onlyask()blocks on a human.POST /api/approvalsfiled both kinds asstatus = 'pending'. So every notification a script fires parks itself in the dashboard's "needs you" queue and stays there for good.Repro (executed on unmodified main, real routers + real libsql)
A normal script run: three
notify()calls for progress, oneask()for the real gate.Exactly one thing is waiting on the operator. The dashboard says four.
It doesn't self-clear either. The only way to get a notification out of the queue is to open it and click Approve & continue (sending a reply to a script that stopped listening) or Kill the loop (killing nothing). A chatty script buries the one
ask()that actually needs a human — which is the whole point of the surface.The fix
apps/pwa/src/routes/approvals.mjsonly. A notify is done the moment it goes out, so ingest files it assentwithsubmitted_atset, and it lands in "moshed" history instead of the queue.ask()is untouched — stillpending, stillsubmitted_at = null, still polled by the CLI the same way.resolve()already no-ops on anything butpending, so a notify can't be flipped by the approve form. The approve page now says "This was a notification — nothing to respond to." instead of rendering an emptyYou replied: "".status = 'pending'/status != 'pending') andpollApproval(which only reacts tosubmitted/killed) both handle the new value with no change. No migration needed —statusis a plain TEXT column and existing rows are untouched.001_init.sqlto keep the documented status vocabulary accurate. Migrations are tracked by filename, so applied databases are unaffected.Tests
New
apps/pwa/test/approvals-notify.test.mjs, 5 tests, with the same skip-guard the other PWA tests use when the app's deps aren't installed.Stash-verified with
git stash push -q -- apps/pwa/src/: 4 of 5 fail unpatched, 5/5 with the fix. The fifth ("an ask() still waits on a human") passes either way on purpose — it's there to lock in that this change doesn't touch the blocking path.Full root suite
npm test: 197 tests / 197 pass / 0 fail (baseline on main measured this run: 192 / 192 / 0).