mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #402] [MERGED] feat(cursor-review): say why a direct-API cell failed instead of discarding it #405
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#405
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/402
Author: @mattmillerai
Created: 10/10/2026
Status: ✅ Merged
Merged: 10/10/2026
Merged by: @mattmillerai
Base:
main← Head:matt/direct-lane-error-visibility📝 Commits (3)
0db11aafeat(cursor-review): say why a direct-API cell failed instead of discarding itfef9377test(cursor-review): execute the cell-outcome step, not just lint itb537a26fix(cursor-review): close the review's six findings on the error reporter📊 Changes
5 files changed (+1126 additions, -10 deletions)
View changed files
📝
.github/cursor-review/README.md(+1 -0)➕
.github/cursor-review/agent-error.py(+310 -0)➕
.github/cursor-review/tests/test_agent_error.py(+737 -0)📝
.github/workflows/cursor-review.yml(+52 -10)📝
docs/callers/cursor-review.md(+26 -0)📄 Description
The hole
Both direct-API lanes send their agent's structured output to a file that is never echoed:
Run cursor reviewhead -c 500 /tmp/review-raw.txt+ all of stderr — diagnosableRun direct OpenAI review> /tmp/codex-events.jsonl— never echoed, never uploadedRun direct Anthropic review> /tmp/claude-result.json— same holeThe redirect is correct — those files hold the agent's reasoning, tool calls and findings, i.e. model output steered by PR-authored text, which
Report cell outcomealready refuses to echo for that reason. The problem is thatcodex exec --jsonreports API-level failures as events on stdout, into that same file. So a failing cell's log read:and the lane's failures had to be inferred from job duration.
Not hypothetical. An OpenAI
insufficient_quota/credit_balance_exhausted— an error that named its own cause and linked the billing page — was discarded exactly this way, and the outage was diagnosed by probing the key by hand instead.The fix
agent-error.pywalks the file and prints, from objects that look like errors, an allowlist of short provider metadata (type,subtype,code,param,status,message) plus a census of event types. Wired intoReport cell outcomein both lanes, on the non-ok branch only.The motivating payload now reads:
Three decisions worth a reviewer's attention
The allowlist is the safety property, not a convenience, and it covers both fields and descent.
result,content,text,input,output,arguments,reasoning,deltaandthinkingare not field-allowlisted, and an allowlistedmessageholding content blocks is skipped rather than flattened. Restricting fields alone was not enough, though. The agent chooses its own MCP tool-callarguments, so a planted{"error": {...}}there would have been printed. The walk therefore descends only through keys a CLI owns (ENVELOPE_KEYS), so it never reaches the model's writable subtrees. An unanticipated provider shape reports only the census, which is the safe way for this to fail.AllowlistTestasserts the absence of prose rather than the presence of fields, so a future key added toERROR_FIELDSthat happens to carry content fails there.An
errorKEY is read on its own terms rather than by heuristic. A provider's error object need not describe itself as an error: OpenAI's saysinsufficient_quota/credit_balance_exhausted, and neither contains the substring"error". The first draft of this script parsed that exact payload and still printed only the envelope's owntype=error— missing the one message worth having.NestedEnvelopeTestpins the shape so the regression cannot come back.The raw file is deliberately NOT uploaded as an artifact. The Anthropic lane has a
Refuse a record carrying the API keyguard that screensfindings.jsonbefore publishing it; shipping the unscreened transcript alongside would route the agent's own prose straight past that guard. That felt like the obvious add-on and I left it out on purpose. When the allowlist plus the census is not enough to explain a failure, the fix is to widen the allowlist, not to publish the file.Safety and failure behaviour
::workflow command::line by replacing disallowed characters, never deleting them, so a crafted value cannot collapse into different text. Every::is broken to: :, runs of three or more colons included, and##[is broken the same way. The--labelis sanitized too.RecursionErroris caught at both decode sites, with a broad guard inmain). Both lanes also call it with|| trueunderbash -e. It runs in analways()step after the cell already failed; a traceback here would replace the diagnosis with a crash.result, the same field that holds the review prose. That lane reportsis_error=trueplus the census, and the caller doc states this gap per lane. Duplicate error objects collapse; the census is capped at 10 kinds.$CURSOR_REVIEW_ASSETS, i.e. the pinned ref of this repo — never the caller's checkout, so a PR cannot rewrite the code reporting on it. Pinned bytest_the_script_is_loaded_from_the_pinned_assets.Tests
test_agent_error.pyhas 65 tests; the.github/cursor-reviewsuite (1400) and.github/cursor-approve(205) pass.StepBehaviourTestextracts the realReport cell outcomebody fromcursor-review.ymland runs it against fixtures for both lanes.WorkflowWiringTestpins that both lanes call it, each on the file its own agent writes (a rename on one side only would leave the diagnosis reading a file nobody writes, andcould not readis a plausible-looking output).I mutation-tested the suite rather than trusting a green run. In the first round, five of six deliberate breakages were caught (dropping the nested-envelope branch → 6 failures; admitting
resultto the allowlist → 3; dropping the depth guard → error; sanitizing by deletion → 1; not breaking::→ 2).The sixth was a miss, and it was my test comment that was wrong: I had claimed the
isinstance(value, bool)guard was what stopped"status": truebeing read as a status code. It isn't —True >= 400is already false, so deleting the guard changes nothing and no test can catch it. The guard stays as a statement of intent for anyone who lowers the threshold, and the comment now says that instead of overstating it.Not in scope
CURSOR_PANEL_MODELSis now misleading on both repos (it names three Cursor models while two are replaced at runtime) — separate cleanup.approve_max_failed_reviewers: 2remains a per-cell count.Provenance
python3 -m unittest discover -s .github/cursor-review/tests -p 'test_*.py': 1400 pass atb537a26.github/cursor-approvesuite: 205 passcheck_workflow_pins.pyandcheck_agents_md.py --root .are cleanci-cursor-review.ymlsets no direct model), so the changed step is exercised only byStepBehaviourTestb537a26)resultis withheld; it reportsis_erroronly🤖 Generated with Claude Code
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.