[PR #426] [MERGED] fix(cursor-approve): name the API error when the direct axis runner fails #426

Closed
opened 2026-10-26 22:39:42 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/github-workflows/pull/426
Author: @mattmillerai
Created: 10/14/2026
Status: ✅ Merged
Merged: 10/14/2026
Merged by: @mattmillerai

Base: main ← Head: matt/axis-direct-api-error-cause


📝 Commits (2)

  • b98f70d fix(cursor-approve): name the API error when the direct axis runner fails
  • 8fd42c9 docs(cursor-approve): say the axis error gate is narrower than the panel's, not identical

📊 Changes

3 files changed (+105 additions, -4 deletions)

View changed files

📝 .github/cursor-approve/tests/test_axis_direct.py (+86 -1)
📝 .github/workflows/cursor-axis-base.yml (+18 -2)
📝 docs/callers/cursor-approve.md (+1 -1)

📄 Description

What

Run the axis (direct) gated its agent-error.py diagnostic on rc != 0. Claude Code reports an API-level failure as:

{"type":"result","subtype":"success","is_error":true,"result":"API Error: 400 ..."}

— note subtype, which reads as success — and exits 0. So the one shape _api_error_cause was written to name never reached the diagnostic.

This gates it on the outcome instead, like the panel's direct cell (Report cell outcome, on status != ok). The gate is narrower, though: it fires only on a nonzero exit or is_error, the two outcomes that can carry a provider cause. A clean result that fails a later guard (num_turns, reply type) has no cause to name, and those guards print their own ::error::.

Why

On a private consumer's PR, the CURSOR_REVIEW_ANTHROPIC_API_KEY org hit its Anthropic spend cap. Both axes uploaded no verdict, so cursor-approve posted CHANGES_REQUESTED with zero findings and the card read:

untrusted axis output, approval withheld (correctness: no output)
untrusted axis output, approval withheld (conformance: no output)

That is indistinguishable from a model that answered nothing. The real cause — 400 You have reached your specified API usage limits. You will regain access on 2026-11-01 at 00:00 UTC — was sitting unread in /tmp/axis-claude-result.json, and finding it meant opening the job log. This is the agent-error.py docstring's own stated purpose ("an Anthropic-lane outage was otherwise undiagnosable without re-running the cell") not being reached on the axis lane.

Verified against the real shape — old gate skips, new gate fires:

error: type=result subtype=success is_error=True api_error_status=400
error: result=API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"You have reached your specified API u…

Security

No new publishing surface:

  • agent-error.py screens the cause for ANTHROPIC_API_KEY and the sk-ant- prefix, and caps it at CAUSE_LIMIT. The bounded, documented channel from #402 is unchanged — deliberately not widened, even though the cap truncates just before the words "usage limits" (api_error_status=400 plus the visible prefix is already unambiguous).
  • jq loads no module from the cwd, so it needs no -I equivalent like the adjacent stdin Python.
  • A missing, non-object or invalid result file yields false and leaves the existing guards to report — it fails closed toward "no extra diagnostic", never toward a crash.

Tests

New ApiErrorDiagnostic class in .github/cursor-approve/tests/test_axis_direct.py — 6 tests that read the real if condition out of the workflow and execute it, so the gate cannot drift back:

  • an API error on a zero exit is diagnosed (the regression)
  • a non-zero exit still is
  • a clean run is not (no happy-path noise)
  • an unreadable/junk/empty result never breaks the gate
  • end to end: the cause reaches the log
  • the guard's terminal message points at the printed cause
.github/cursor-approve/tests   310 tests  OK
.github/cursor-review/tests   1589 tests  OK
check_workflow_pins.py                    OK

No input was added or removed, so no caller needs to change. docs/callers/cursor-approve.md gains one sentence on the now-named cause.

Provenance

Authored by: agent-work loop
Verified:

  • .github/cursor-approve/tests: 310 tests OK at head (includes the 6 ApiErrorDiagnostic tests that run the real gate)
  • check_workflow_pins.py, check_agents_md.py --root ., check-org-repo-literals.sh: all pass
  • CI green on head 8fd42c9 (all checks pass; no merge-queue run yet)

Deviations:

  • Review round: dropped the "exactly" parity claim with the panel's direct cell (workflow comment + test docstring). The gate stays narrower on purpose, because widening it would add no diagnosable cause.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/github-workflows/pull/426 **Author:** [@mattmillerai](https://github.com/mattmillerai) **Created:** 10/14/2026 **Status:** ✅ Merged **Merged:** 10/14/2026 **Merged by:** [@mattmillerai](https://github.com/mattmillerai) **Base:** `main` ← **Head:** `matt/axis-direct-api-error-cause` --- ### 📝 Commits (2) - [`b98f70d`](https://github.com/Comfy-Org/github-workflows/commit/b98f70dd2fd8c58b2ffe2e3327488c130123bfaf) fix(cursor-approve): name the API error when the direct axis runner fails - [`8fd42c9`](https://github.com/Comfy-Org/github-workflows/commit/8fd42c972e5357014cd7065e1855950dff74dc52) docs(cursor-approve): say the axis error gate is narrower than the panel's, not identical ### 📊 Changes **3 files changed** (+105 additions, -4 deletions) <details> <summary>View changed files</summary> 📝 `.github/cursor-approve/tests/test_axis_direct.py` (+86 -1) 📝 `.github/workflows/cursor-axis-base.yml` (+18 -2) 📝 `docs/callers/cursor-approve.md` (+1 -1) </details> ### 📄 Description ## What `Run the axis (direct)` gated its `agent-error.py` diagnostic on `rc != 0`. Claude Code reports an API-level failure as: ```json {"type":"result","subtype":"success","is_error":true,"result":"API Error: 400 ..."} ``` — note `subtype`, which reads as **success** — and **exits 0**. So the one shape `_api_error_cause` was written to name never reached the diagnostic. This gates it on the **outcome** instead, like the panel's direct cell (`Report cell outcome`, on `status != ok`). The gate is narrower, though: it fires only on a nonzero exit or `is_error`, the two outcomes that can carry a provider cause. A clean result that fails a later guard (`num_turns`, reply type) has no cause to name, and those guards print their own `::error::`. ## Why On a private consumer's PR, the `CURSOR_REVIEW_ANTHROPIC_API_KEY` org hit its Anthropic spend cap. Both axes uploaded no verdict, so cursor-approve posted CHANGES_REQUESTED with **zero findings** and the card read: ``` untrusted axis output, approval withheld (correctness: no output) untrusted axis output, approval withheld (conformance: no output) ``` That is indistinguishable from a model that answered nothing. The real cause — `400 You have reached your specified API usage limits. You will regain access on 2026-11-01 at 00:00 UTC` — was sitting unread in `/tmp/axis-claude-result.json`, and finding it meant opening the job log. This is the `agent-error.py` docstring's own stated purpose ("an Anthropic-lane outage was otherwise undiagnosable without re-running the cell") not being reached on the axis lane. Verified against the real shape — old gate skips, new gate fires: ``` error: type=result subtype=success is_error=True api_error_status=400 error: result=API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"You have reached your specified API u… ``` ## Security No new publishing surface: - `agent-error.py` screens the cause for `ANTHROPIC_API_KEY` and the `sk-ant-` prefix, and caps it at `CAUSE_LIMIT`. The bounded, documented channel from #402 is **unchanged** — deliberately not widened, even though the cap truncates just before the words "usage limits" (`api_error_status=400` plus the visible prefix is already unambiguous). - `jq` loads no module from the cwd, so it needs no `-I` equivalent like the adjacent stdin Python. - A missing, non-object or invalid result file yields false and leaves the existing guards to report — it fails closed toward "no extra diagnostic", never toward a crash. ## Tests New `ApiErrorDiagnostic` class in `.github/cursor-approve/tests/test_axis_direct.py` — 6 tests that read the real `if` condition out of the workflow and execute it, so the gate cannot drift back: - an API error on a **zero** exit is diagnosed (the regression) - a non-zero exit still is - a clean run is not (no happy-path noise) - an unreadable/junk/empty result never breaks the gate - end to end: the cause reaches the log - the guard's terminal message points at the printed cause ``` .github/cursor-approve/tests 310 tests OK .github/cursor-review/tests 1589 tests OK check_workflow_pins.py OK ``` No input was added or removed, so no caller needs to change. `docs/callers/cursor-approve.md` gains one sentence on the now-named cause. ## Provenance **Authored by:** agent-work loop **Verified:** - `.github/cursor-approve/tests`: 310 tests OK at head (includes the 6 `ApiErrorDiagnostic` tests that run the real gate) - `check_workflow_pins.py`, `check_agents_md.py --root .`, `check-org-repo-literals.sh`: all pass - CI green on head `8fd42c9` (all checks pass; no merge-queue run yet) **Deviations:** - Review round: dropped the "exactly" parity claim with the panel's direct cell (workflow comment + test docstring). The gate stays narrower on purpose, because widening it would add no diagnosable cause. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-26 22:39:42 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/github-workflows#426
No description provided.