Repository navigation
fix: audit long task replacements without an endless Slack retry loop - #29
Merged
Merged
Conversation
A queued task's instruction is shown on a Slack card in both channels. A replacement must update both cards before the task may be dispatched. The cards are posted with chat.postMessage, which accepts long text. Updates went through chat.update, which rejects text above roughly 4,000 characters with msg_too_long. A replacement of about 4,700 characters therefore failed on every 3-second reconciliation sweep, indefinitely. That cost two Slack calls per sweep, tens of thousands of log lines a day, and much of the chat.update rate budget. Dispatch requires a fully audited revision, so the replaced task could never be claimed. The card updater moves to daemon/team-audit.mjs (createPayloadAuditUpdater, with the unchanged teamTaskPayloadText): - A revision too long to edit in place is posted once, in full, as a new card in each channel. It is recorded per side in payloadRevisionCards so a retry never posts it twice. The original card is edited into a short pointer to it. The same fallback applies when Slack reports msg_too_long below the local threshold. - A failing update backs off per task, from 30 s doubling up to 30 min. A newer revision is attempted at once. - The rendered revision is still captured first and recorded only after both cards succeed, and updates remain serialized per task, so dispatch never crosses a mixed-card state. Tests (test/team-audit.test.mjs) use a fake Slack that enforces the update limit: - a 4,703-character replacement is audited, and the revised task can be claimed; - a short revision is still edited in place; - the threshold fallback works; - a partial failure is retried without reposting; - a permanently failing card makes at most 6 attempts over 10 simulated minutes, against 200 before; - the rendered version is the one recorded. Restoring the old in-place-only behaviour, or removing the backoff, the repost guard, the version capture or the fallback, each turns the tests red. Full suite: 591 pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Xs8AVBVrTGfpzUMiCQp2R
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.
Summary
A queued task replaced with an instruction longer than about 4,000 characters could never be dispatched, and SAB retried its failing Slack card update every 3 seconds, forever.
This PR posts oversized revisions as new cards and backs off failing card updates.
Root cause
chat.postMessage, which accepts long text.sab team replacerevision updates both cards throughchat.update, which rejects text above roughly 4,000 characters withmsg_too_long.In the live daemon, one replaced (later cancelled) task produced:
chat.updatecalls a minute;Successful replacements in the journal were at most 3,309 characters; the failing one is 4,703.
Fix
The card updater moves to
daemon/team-audit.mjsascreatePayloadAuditUpdater();teamTaskPayloadText()moves unchanged.payloadRevisionCards, so a retry never posts it twice. The original card is edited into a short pointer to it. The same fallback applies when Slack reportsmsg_too_longfor shorter text.Tests (
test/team-audit.test.mjs, fake Slack enforcing the update limit)msg_too_longbelow the local threshold falls back to a revision card.Each mutation was applied on its own:
msg_too_longValidation
npm test: 591 pass.npm run check,npm run audit(0),node --check, Pi extension andshellcheckall pass. Every daemon named import resolves.Deployment notes
payloadRevisionCardsis additive.🤖 Generated with Claude Code
https://claude.ai/code/session_011Xs8AVBVrTGfpzUMiCQp2R