fix: surface the shadow review's own comment on a plain PR reassignment #479
No reviewers
Labels
No labels
Agent
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
1 participant
Notifications
Total time spent: 12 minutes 8 seconds
Due date
igor
12 minutes 8 seconds
No due date set.
Dependencies
No dependencies set
Reference
joshtronic/igor!479
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/476-fix-rework-pickup-ignores-feedback-when-the"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this PR does
review_reassignment_feedback_section(lib/review.sh): on areassignment where
pending_rc_bodyis empty (last shadow verdict wasCOMMENT, notREQUEST_CHANGES), find the most recent shadow-reviewcomment (bot-authored, filtered out of every other comment feed) plus
any comment posted by someone else after it, and fold that into the
rework prompt. Empty when there's no shadow-review comment on the
thread at all, so the existing "no changes made" no-op stays correct.
bin/tick.sh's reassignment pickup path, only on thenon-binding branch (the RC-binding branch already carries its own
findings via
BINDING_RC_BODY).strip forged structural delimiters/headings before it reaches the
prompt -- same treatment
review_dismissals_sectionalready applies tobot-authored, diff-derived text just above it in the same file.
bin/test-review.sh: reassignment with a COMMENT-verdictshadow comment (+ a post-review operator comment) surfaces both; no
shadow-review marker at all leaves the section empty (unchanged from
today); a comment predating the review, an unmarked bot comment, and a
superseded older round are all excluded; a forged delimiter inside the
shadow comment is neutralised; plus fetch-failure / malformed-payload
degrade-to-empty coverage matching the sibling function's contract.
Test plan
make testpasses (full suite, including the new/extendedbin/test-review.shcases)make lintpasses (shellcheck + mdl)inside a live PR-reassignment pickup against a real Forgejo PR, which
isn't reachable from this worktree.
Closes #476
🤖 Review —
REQUEST_CHANGES(automated)CI for
44770aa3: successAdds
review_reassignment_feedback_section(lib/review.sh), wires it into the plain-reassignment prompt inbin/tick.sh, and covers the new function with unit tests. The library function itself is well-built and the behavioural tests are genuinely good — my block is about the one line that makes the fix actually work being both untested and mis-described.Blocking
bin/test-review.sh(new "source assertions" block) claims coverage it doesn't provide. The comment says the test pins that the plain-reassignment branch "actually calls the helper and folds its output into the prompt", but the only assertion isgrep -q 'review_reassignment_feedback_section "\$PR_REPO" "\$PR_NUMBER"' "$TICK".That greps the call site only. Nothing asserts
${PR_REASSIGNMENT_FEEDBACK}appears in the heredoc — which is the single line the whole fix depends on. Delete that interpolation and every test in this PR still passes, silently restoring igor#476.Fixed looks like: a second assertion that
${PR_REASSIGNMENT_FEEDBACK}is present inbin/tick.shand that it is in the non-binding heredoc (e.g.awk/sedrange from theelseofif [ -n "$BINDING_RC_BODY" ]to itsEOF), or — if you can't scope it — trim the comment so it doesn't claim more than it checks.Related, and why the above matters: from the diff alone I cannot confirm the interpolation landed in the non-binding heredoc.
PR_REASSIGNMENT_FEEDBACKis assigned before both branches and is guarded to be empty whenBINDING_RC_BODYis set, so if${PR_REASSIGNMENT_FEEDBACK}were placed in the RC-binding heredoc the feature would be permanently dead with no test failing. Line numbers make the else-branch likely, but "likely" isn't verification, and the checklist item ("only on the non-binding branch") is exactly the kind of claim a test should pin.Non-blocking
lib/review.sh:review_reassignment_feedback_sectionreturns empty (warning only) whenbotis empty. IfBOT_USERisn't the in-scope variable name at that point inbin/tick.sh— or differs in case from the comment author'slogin, since.user.login == $botis an exact match — the feature silently no-ops with only a journal line. Worth confirming it matches whateverreview_dismissals_section's existing call site uses.lib/review.sh:afteruses string comparison oncreated_at(>andsort_by). Fine for uniform...Ztimestamps; misorders if Forgejo ever emits offset forms, and strictly excludes a comment posted in the same second as the review. Probably acceptable — flagging so it's a known assumption.lib/review.sh: the heading/delimiter scrub is an allowlist of three headings plus the AGENT fence. Other prompt headings (## Formal review bodies, the ISSUE/DISMISSAL fences) aren't stripped, andafter_text(arbitrary non-bot commenters) is emitted outside the fence entirely. Defensible if that matches howPR_ISSUE_COMMENTSalready treats human text, but the asymmetry is worth a sentence in the comment rather than leaving a reader to infer it.bin/test-review.sh: the malformed-payload block doesunset -f logand then re-sourceslib/review.shwith a# shellcheck source=directive. Iflogis defined in a different lib (log/common), that re-source doesn't restore it — it only happens to be harmless because nothing after that point callslog. Either restore the reallogproperly or drop the re-source.bin/test-review.sh, first case (no bot user -> no section): unlike the sibling failure cases it doesn't redirect stderr, so the warning leaks into test output. Cosmetic.bin/tick.sh: when the section is non-empty,--- END UNTRUSTED AGENT TEXT ---abuts## Issue-level commentswith no blank line (the empty-case behaviour is correctly unchanged — nice trick). Prompt-readability nit only.Otherwise
Scope is tight, the diff is proportionate to the task, the comments state non-obvious why (the "every other feed filters bot text out" rationale earns its place), CI is green, and the rest of the checklist matches the diff — every enumerated test case is actually present in
bin/test-review.sh.Independent review by the harness on
claude-opus-5(effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.🔧 Rework — round 1 (automated)
Addressed the review on
claude-opus-5at effort high — 1 new commit(s).🧑⚖️ Rework — findings dismissed (automated)
Dismissed:
BOT_USERmay not be the in-scope name / may differ in caseChecked; it matches exactly.
BOT_USERis set once atbin/tick.sh:159fromforgejo_resolve_bot_user,hard-failed at 160 if empty, and
exported at 168. It is in scope for thewhole file, including the reassignment pickup at 3716.
review_dismissals_section's call site isreview_build_prompt, whichbin/tick.sh:3038calls with"${BOT_USER:-}"as its 9th argument -- the samevariable, same
:-guard, threaded through to the same.user.login == $bexact match. My call passes
"${BOT_USER:-}"directly.forgejo_resolve_bot_userreads.loginfrom/user(lib/forgejo.sh),so it is the API's own spelling of the login, compared against
.user.loginfrom the same API. Not a hand-typed constant, so there is no case to drift.
If that comparison were wrong, the dismissals section (shipped in igor#456,
live since) would have been silently dead the whole time too -- and it isn't.
Dismissed: the
unset -f log+ re-source doesn't restorelogIt does restore it, and it is the pre-existing pattern in this same file.
bin/test-review.shsources exactly one library at top level:. "$HERE/../lib/review.sh"(line 13).lib/forgejo.shis sourced only insidea subshell (the igor#444 arity guard at lines 25-31), so it never defines
login the test's own shell. That makes
lib/review.sh:35's fallback --if ! declare -F log >/dev/null; then log() { ... >&2; }; fi-- the onlylogthe suite ever has. Afterunset -f logthedeclare -Fguard failsagain, so re-sourcing re-defines it. Verified: the identical
unset -f log+ re-source pair already exists at lines 250-252 for thedismissals block, with the comment "drop the capture stub; lib/review.sh's real
log() is restored below", and the block immediately after it is the
load-bearing "log must go to stderr, not stdout" assertion -- which only passes
because the re-source restored it.
The rest of the findings were addressed in the commits on this branch. The reviewer will re-review the new head.
The source assertion only grepped the call site, so deleting ${PR_REASSIGNMENT_FEEDBACK} from the prompt -- the one line the fix hangs on -- left every test green and igor#476 restored. Assert the interpolation is inside the plain-reassignment heredoc and NOT the RC-binding one (where the variable is always empty, so the feature would be permanently dead), identifying each heredoc by its opening sentence rather than by line order, with a guard that those sentences still match. Also: keep a blank line between the section and the heading that follows it, and state the created_at string-ordering assumption plus why the post-review human comments sit outside the untrusted fence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>🤖 Review —
COMMENT(automated)CI for
91f887b5: successAdds
review_reassignment_feedback_sectiontolib/review.sh, wires it into the plain (non-RC) reassignment prompt inbin/tick.sh, and covers it with unit + source-level tests. CI is green, the diff is tightly scoped to igor#476, and every checklist item maps to real code in the diff. I found no defect I can demonstrate from the diff — but two couplings I can't verify from here, so I'm leaving it to a human rather than approving.Things I could not verify (the reason this isn't an APPROVE)
lib/review.sh:~412— the whole feature hinges on the shadow verdict being an issue comment returned byforgejo_pr_commentswhose body matches the regextest("review sha="). Neither half is pinned by a test: the fetch is stubbed in every case, so ifdo_review_tickposts the verdict as a formal PR review (whichPR_REVIEW_BODIESsuggests exists as a separate feed), or ever changes the marker spelling, this function silently returns empty and the bug from igor#468 is back with all tests green. That is exactly the failure mode the author wrote theheredoc_hasassertion to prevent for the wiring — the same treatment applied to the marker string (a source assertion that the emitting path intick.shproduces a body matchingreview sha=) would close the loop. Worth a human eyeball ondo_review_tick's posting call before merge.verdict=APPROVE. On a plain reassignment following an APPROVE, the prompt asserts "the shadow-review comment below is likely the actionable feedback behind that reassignment" over a review that found nothing. Not obviously harmful (any real human comment still shows up in the "after" block), but the framing is wrong in that case and it's cheap to gate onverdict=(COMMENT|REQUEST_CHANGES)in the marker.Non-blocking notes
bin/tick.sh:3833— in the non-empty case the section's## Feedback since the last shadow reviewheading lands directly under the last line of${PR_REVIEW_BODIES}with no blank line between them (the trailing newline was added at the end of the section, not the start). Cosmetic in a prompt, but the author explicitly reasoned about spacing here so it looks unintended.after_textare duplicated: they appear both in this new section and in${PR_ISSUE_COMMENTS}below. Probably fine (recency emphasis) but worth a conscious call.created_at(.created_at > $last_shadow.created_at) is unsound for non-Zoffsets and drops same-second comments — already documented in-code as an accepted trade-off; I agree it's acceptable.Dismissals from the prior round — both check out. The
BOT_USERscoping argument is consistent with thereview_build_promptcall site shown in the diff's neighborhood, and theunset -f log/ re-source pattern is verifiably the pre-existing one in the same file (the dismissals block does the same thing and its stderr assertion depends on it). Dropping both.Test coverage — genuinely good for a shell change: guard case (no marker → empty), the core bug, post-review operator comment, pre-review exclusion, superseded round, unmarked bot comment, forged-delimiter neutralisation, fetch failure and malformed payload. The "guard the guard" marker-presence check before the heredoc assertions is the right instinct; extending it to the marker string (above) is the one gap.
Independent review by the harness on
claude-opus-5(effort: high). The human reviewer is requested once Igor has reviewed; a human still merges.