fix(delivery): preserve assignee on failed result so retries stay routable - #46
Merged
Conversation
…table Resolve the retry-vs-escalate contradiction in the result deliverer. A failed result cleared assignee while the comment promised retry via trigger_count/max_triggers — the task became unroutable and the retry silently never fired (the 2026-08-10 silent job-failure wedge in agent-task-executor, where 22 tasks sat unassigned at the retry cap). failed now preserves assignee (infra failures are transient — crash, timeout, OOM) so the retry path stays reachable, and only at cap exhaustion escalates: assignee cleared to "" (operator-inbox signal), previous_assignee recorded (spec 027), phase left as the resume cursor. needs_input unchanged (immediate escalation). Applies to both the Kafka and file result-deliverer paths.
There was a problem hiding this comment.
Now I have enough information. Let me compile the findings.
Key verification results:
- Counterfeiter mock exists:
mocks/delivery-content-generator.goexists with the correct directive atcontent-generator.go:14 - Suite file exists:
delivery/delivery_suite_test.gois present (sosuite-test-file-requiredfindings are false positives) TriggerCount()/MaxTriggers()are real methods with proper handling of zero/unset values (defaults to 0 and 3 respectively) - the concern is addressed- glog usage is pre-existing in this project (not new), so exempt from
slog-not-glog-in-new-projects - Counterfeiter
*Returns()methods are genuinely void (no return) - exempt fromno-bare-error-call - Both code paths (
content-generator.goandresult-deliverer.go) correctly implementtriggerCountAtCapwith the same logic
Traceability: Candidate rules from Step 4b-i glob: The changed files are all Go files in delivery/, which matches the project rules. The mechanical findings from the pre-computed funnel (66 findings) are the authoritative set.
Now producing the consolidated report:
PR Review: fix(delivery): preserve assignee on failed result so retries stay routable
Must Fix (Critical)
None in the new code. The architectural debt in result-deliverer.go (package-level function calls in business logic) exists in both the old and new code and is not introduced by this PR.
Should Fix (Important)
None.
Nice to Have (Optional)
delivery/content-generator.go:128—triggerCountAtCaploops over frontmatter map without actx.Done()check. This is a short loop (one iteration overfmMap), so the practical risk is minimal, but the pattern should be consistent. (rule:go-context/cancel-check-in-loop)
Selector mode traceability:
- Candidates: 66 findings across go-architecture-assistant, go-context-assistant, go-quality-assistant, go-test-quality-assistant
- Applicable: 2 rules triggered by the diff (
go-context/cancel-check-in-loopvia the short loop at line 128,go-cli/slog-not-glog-in-new-projectsfor glog import — though exempt) - False positives addressed: counterfeiter directive present + mock exists (
go-testing/counterfeiter-mocks-required), suite file exists (go-testing/suite-test-file-required), counterfeiter*Returns()methods are void (go-testing/no-bare-error-call)
precommit skipped (selector mode) — CI covers lint+test
Notes:
- The bug fix logic is correct:
AgentStatusFailednow preservesassigneebelow the trigger cap and only clears it at cap exhaustion, withprevious_assigneerecorded. Both thecontent-generator.go(file path) andresult-deliverer.go(Kafka path) implement the same contract viatriggerCountAtCap. TriggerCount()returns 0 when absent;MaxTriggers()returns 3 when absent — the>=comparison handles the zero/unset edge case correctly.- The
content-generator.gopath correctly does NOT callbuildFailureSectionforAgentStatusFailed— it delegates to the caller (theReplaceOrAppendSectioninGenerate). Theresult-deliverer.gopath correctly does NOT add a## Failuresection (the generator already included it in its output). - The existing architectural debt in
result-deliverer.go(23 package-level function calls) is pre-existing and not introduced by this PR.
{
"verdict": "approve",
"summary": "Bugfix correctly implements retry-vs-escalate contract: failed results preserve assignee below the trigger cap so retries stay routable, and only escalate (assignee cleared, previous_assignee recorded) at cap exhaustion. Both code paths (content-generator and Kafka deliverer) implement the same logic. The architectural debt (package-level function calls in result-deliverer.go) is pre-existing and not introduced by this PR. The one SHOULD- tier finding (missing ctx.Done check in short loop) is pre-existing in the changed code.",
"comments": [
{
"file": "delivery/content-generator.go",
"line": 128,
"severity": "minor",
"message": "Short for-loop in triggerCountAtCap does not check ctx.Done(). While the loop is brief (one map iteration), the go-context/cancellation-in-loops pattern should be applied consistently. *(rule: go-context/cancel-check-in-loop)*"
}
],
"concerns_addressed": [
"correctness: triggerCountAtCap logic confirmed correct — TriggerCount() defaults to 0, MaxTriggers() defaults to 3, >= comparison handles zero/unset",
"correctness: both code paths (content-generator.go and result-deliverer.go) implement the same triggerCountAtCap logic and stay in sync",
"correctness: getFrontmatterField returns empty string on absent/non-string values — yaml edge cases handled",
"correctness: previous_assignee only recorded inside triggerCountAtCap branch — confirmed as the only escalation trigger"
]
}
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.
Resolve the retry-vs-escalate contradiction in the result deliverer.
Bug: a
failedresult clearedassigneewhile the comment promised retry viatrigger_count/max_triggers. Clearing the assignee made the task unroutable (the executor skips empty-assignee tasks), so the retry silently never fired —skipped_trigger_capstayed 0 while 22 tasks sat attrigger_count2–4 (the 2026-08-10 silent job-failure wedge in agent-task-executor).Fix (matches spec 010/021/027):
failed(infra/transient: crash, timeout, OOM) → preserveassignee, task stays routable, retry viatrigger_count/max_triggersfires.assigneecleared to""(operator-inbox signal),previous_assigneerecorded (spec 027), phase left as the resume cursor.needs_inputunchanged (immediate escalation — task-wrong, not transient).applyResultFrontmatterhelper keepsDeliverResultunder the funlen limit).Tests updated/added: below-cap preserves assignee, at-cap escalates with
previous_assignee, for both deliverers.make precommitgreen (0 lint issues, all packages pass).