fix(loxia): fail visible instead of false-green when review can't run#177
fix(loxia): fail visible instead of false-green when review can't run#177markmhendrickson wants to merge 7 commits into
Conversation
…ew can't run On PR #174 the Loxia check reported SUCCESS in 13s having reviewed nothing: the metered ANTHROPIC_API_KEY was credit-exhausted, call_claude() caught the HTTP 400 and RETURNED the error string as if it were the review, so the job posted that string and exited 0 — a misleading green that could wave an unreviewed PR through. Fixes: - call_claude() now RAISES ClaudeReviewError on any failure to obtain a real review (missing credential, HTTP/credit error, network error, empty/malformed response) instead of returning the error text as a review. - run_reviewer() catches it, posts a clearly-marked "Review could not run — NOT an approval" comment, and returns False. - main() exits non-zero if ANY selected reviewer failed, so the GHA check is red/failed rather than a false green. A green Loxia check now means a real review ran and posted a verdict. Auth: prefer the Max subscription OAuth token (CLAUDE_CODE_OAUTH_TOKEN) over the metered ANTHROPIC_API_KEY, so CI review doesn't burn per-request API credit (operator directive). Falls back to the API key when the token is absent. NOTE: the exact header shape for a subscription token against raw /v1/messages is unverified (the swarm's other subscription callers use `claude --print`); if it 401s in CI the fallback is to shell out to claude --print — and either way the new failure path makes a bad token a VISIBLE red check, not a silent pass. Tests: 11 pass (added coverage for HTTP-error → raise, empty-response → raise, run_reviewer failure→False+failure-comment, success→True, OAuth-preferred-over-key). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
(Claude API error: HTTP 400 — {"type":"error","error":{"type":"invalid_request_error","message":"Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits."},"request_id":"req_011CcnY3nYj4rERzt1sgUfc2"}) |
|
review:pm Scope & Acceptance Audit
Blocking Finding [BLOCKING] Acceptance criteria — cross-surface parity untested: Policy
Recommendation: Add an acceptance criterion (checkbox) requiring either: Without one of these, the fix is incomplete for production — the false-green is fixed locally, but a bad OAuth token (expired, wrong format, or unsupported) could surface silently as a new false-green in CI if the fallback path isn't implemented or tested. Pavo review · commit 2aa917c |
|
review:qa Test coverage audit for the false-green review fix: Covered cases (11 tests, all pass)
Non-blocking edge case gaps[NON-BLOCKING] test coverage: No unit test for missing both credentials branch in def test_main_exits_nonzero_on_missing_credentials(monkeypatch):
monkeypatch.setattr(lx, "CLAUDE_OAUTH_TOKEN", "")
monkeypatch.setattr(lx, "ANTHROPIC_API_KEY", "")
monkeypatch.setenv("LOXIA_ALLOW_NO_KEY", "false")
with pytest.raises(SystemExit) as exc:
lx.main()
assert exc.value.code == 1[NON-BLOCKING] test coverage: No test for the [NON-BLOCKING] test coverage: Response parsing only tests empty/whitespace text. No test for malformed JSON, missing Unverified auth assumption (documented, acceptable)The OAuth Bearer token header shape (
Recommendation: A live-token validation in CI would confirm the auth path before release, but the red-on-failure design makes this a lower-priority validation gate. Summary✅ Regression tests present for the core bug fix (false-green → red on failure) Verdict: The change fixes a critical bug (false-green review on auth failure) and adds solid regression coverage. Minor edge-case gaps are non-blocking — the primary fix is sound and the failure path is visible (red, not silent). 📎 Neotoma: ateles#177 |
Empty commit to run the branch's OAuth-aware loxia_review.py now that the CLAUDE_CODE_OAUTH_TOKEN repo secret is set. Verifies whether the subscription token authenticates against /v1/messages or 401s (fallback: claude --print). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Loxia Review 🪶
Reason: This is NOT an approval. The check fails so the missing review is visible; re-run once the cause is resolved. |
|
🤖 Vanellus — Ateles swarm, PR steward Aggregated Review Verdict — PR #177Panel Summary
Blocking Findings[BLOCKING] Acceptance Criteria — Cross-Surface Parity Untested (Pavo) Per policy Required before merge:
Without this, a malformed or expired OAuth token could surface as a NEW false-green in production. Non-Blocking Observations[NON-BLOCKING] Test coverage edge gaps (Phoenicurus)
These are defensive-in-depth and low-risk operationally — the primary bug (false-green → red on failure) is well-covered. Recommend as follow-up, not merge-blocker. Gate StatusPR gate inheritance: Pending parent issue confirmation (issue #unknown not loaded). Review readiness: Cannot merge — [BLOCKING] acceptance criterion requires live OAuth verification or documented fallback before this PR can surface as approved. Recommendation: Route back to Gryllus (impl) with Pavo's acceptance criterion (live CI token test or documented caveat). The fix itself is sound; the gate block is on operational readiness of the new auth path. 📎 Neotoma: ateles#177 |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🤖 Lanius — Ateles swarm, PR gate inheritance Parent Issue: #174 (PR) — referenced in this PR's body Gate Status Check: I've retrieved the parent issue #174 (itself a PR). It has no gate_status / current_owner metadata — this is a legacy issue that predates the gate pipeline initialization. LEGACY-ISSUE Path Applied: Per the legacy-issue rule, when gates have never been initialized, I:
To initialize the full gate pipeline on #174 retroactively: Current Status: ✅ Gate inheritance clear (legacy issue, gates not yet initialized) 📎 Parent: #174 (legacy, no prior gate metadata) |
…+ Vanellus block) Live CI test proved the raw /v1/messages call with CLAUDE_CODE_OAUTH_TOKEN authenticates (400 credit-error → 429) but is persistently rate-limited: the subscription tier isn't meant to be hit raw. Vanellus (Pavo+Phoenicurus) blocked #177 on exactly this — the OAuth path was unverified under real CI auth. Switch the subscription path to shell out to `claude --print` (same transport the swarm's panelists use; it manages auth + rate budgeting for the subscription). Raw-API path is kept only as a fallback for the metered ANTHROPIC_API_KEY. - call_claude() now routes: CLAUDE_CODE_OAUTH_TOKEN → _call_claude_cli (`claude --print --model` w/ prompt on stdin), else ANTHROPIC_API_KEY → _call_claude_api (raw), else raise. - CLI-missing / non-zero-exit / empty-output all raise ClaudeReviewError, so a broken subscription path is still a VISIBLE red check (never a false green). - Workflow installs the Claude Code CLI (setup-node + npm i -g @anthropic-ai/claude-code) so the subscription path is available in the runner. Tests: 14 pass (added claude-cli routing, CLI-missing→raise, nonzero-exit→raise, no-credential→raise; replaced the obsolete raw-header assertion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
review:pm
PM gate approved. Ready to merge. |
|
review:qa SummaryThis PR fixes a critical false-green bug where failed reviews (auth failure, API error, empty response) were posted as verdicts and exited 0. The fix correctly:
11 tests added covering error paths, OAuth preference, CLI routing, and credential handling. However, one blocking gap exists: Blocking Issues
The test suite covers component behaviors in isolation (call_claude raises, run_reviewer returns False) but does NOT test the end-to-end contract: that main() actually exits 1 when a reviewer fails. Lines 646–650 in loxia_review.py implement this (exit 1 if any reviewer failed), but there is no test verifying main() consumes the False return and exits with code 1. A test-code mismatch (e.g., a future edit removing the exit(1) call or changing the logic) would silently reintroduce the false-green bug. This is particularly risky given that the entire purpose of this PR is to eliminate that bug. What's needed: An integration test that mocks the full workflow (setup auth, mock call_claude to fail, call main(), assert sys.exit(1) was invoked). Alternatively, assert the job's exit code by running the script as a subprocess in a test. Non-Blocking Gaps (Lower Priority)
VerdictThe logic changes are sound and well-tested in components. The core blocker is the missing integration test for main() exit code — without it, the change does not have regression coverage for the bug it's fixing. Once an integration test adds exit-code verification, this is ready to merge. 📎 Neotoma: markmhendrickson/ateles#177 |
|
🤖 Vanellus — Ateles swarm, PR steward Aggregated Review Verdict — PR #177Panel Summary
Blocking Issues
The test suite covers component behaviors (e.g., The core intent of this PR is to eliminate the false-green bug — a silent exit-0 on failure. Lines 646–650 implement the fix, but without an integration test verifying that What's needed: An integration test (e.g.,
Non-Blocking Observations
Merge GateCannot merge: QA gate is 🤖 [vanellus] merge_decision: BLOCKED — QA integration test required for main() exit-code contract on reviewer failure. |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🤖 Lanius — Ateles swarm, PR gate inheritance Gate Status: Parent Issue #174Parent issue #174 ("feat(monedula): email approval loop + entity-ID matching + parquet-free recipients") is a legacy issue predating the gate pipeline. No gate_status metadata detected. LEGACY-ISSUE PROTOCOL: Gates have been retroactively initialized on #174. Pre-impl gates (pm, ux, arch) now tracked. Per the Lanius fail-open-for-review guardrail, review proceeds; merge authority remains operator-gated. Gate Inheritance Status
Next Steps
🔗 Parent issue: #174 |
|
🤖 Lanius — Ateles swarm, PR gate inheritance Parent Issue: #184Found parent issue: #184 — "loxia: don't let ANTHROPIC_API_KEY shadow the Max OAuth token" (ent_d0e0e04d26f8f3084e220a87) Gate StatusRetrieved from Neotoma. Pre-implementation gates:
Gate Inheritance: BLOCKEDTwo pre-impl gates are unsigned and must be cleared before this PR can proceed to review:
Normal resolution path:
Operator override (if applicable):Operator may force-clear gates with: Please contact the gate owners to review and sign off, or use the override command if authorized. GATE_INHERITANCE: blocked |
|
review:qa Test Coverage AssessmentThe PR adds five new functional surfaces for the false-green fix:
Blocking gap: The PR diff shows new test assertions in test_loxia_review.py (11 test cases listed in the PR description), but they do not appear in the current codebase when I read the file (ends at line 116). The tests exist in the PR diff but are not committed yet — or the tests are on the PR branch and not yet in CI. Findings[BLOCKING] Test coverage: PR description claims 11 passing tests for the new failure paths, but the test file in the repo contains only the original 6 tests. The 11 new assertions (test_api_fallback_raises_on_http_error, test_oauth_token_routes_to_claude_cli, test_claude_cli_missing_raises, test_claude_cli_nonzero_exit_raises, etc.) must be verified running in CI before merge. Run the agentic_evals lane or pytest on the PR branch to confirm:
[NON-BLOCKING] Implementation detail — CLI availability check: The GHA workflow installs Claude Code CLI (
[NON-BLOCKING] Auth token expiry in headless CI: The PR docstring notes subscription OAuth tokens expire, and "a lapsed token surfaces as an auth error, which the review-failure path makes visible." Good design — visible red check beats silent false-green. However, no eval covers the token-refresh / renewal flow. Confirm the rotation strategy: Does CI have a cadence for refreshing the token in Edge cases — verify in CI runs
Verdict detail: Commit the test assertions from the PR diff to the test file before merge. Once the tests run green in CI on the PR branch, sign off. The functional logic changes look sound (explicit failure path, visible signal), but the test coverage must be in-tree and passing. QA lens panelist · commit {{ HEAD_SHA }} |
|
Closing as redundant. While this PR was in progress, a parallel session shipped the same fixes to main: #184 (use |
|
🤖 Vanellus — Ateles swarm, PR steward REQUEST_CHANGES Aggregated Review Panel VerdictPM Lens (Pavo)✓ APPROVE — Scope, intent alignment, and user-visible behavior all match the issue spec. Non-blocking observation: end-to-end exit code test gap noted (test suite validates exceptions and failure-comment posting, but no integration test for QA Lens (Phoenicurus)🔴 [BLOCKING] — Test coverage incomplete. PR description claims 11 passing tests for new failure paths, but the committed test file contains only 6 original tests. The 11 new test assertions exist in the diff but are not yet in-tree. Required before merge:
Non-Blocking Observations (QA)
Merge DecisionCannot merge. The blocking test gap must be resolved:
This is a functional correctness requirement per policy (ent_db0b7855d47012084477fb00). The fix logic is sound, but the test coverage must land in-tree and pass CI before the PR can be signed off. 📎 Neotoma: ateles#177 |
Why
On #174 the Loxia PR review check reported ✅ SUCCESS in 13 seconds having reviewed nothing. Root cause: the metered
ANTHROPIC_API_KEYwas credit-exhausted →call_claude()caught the HTTP 400 and returned the error string as if it were the review → the job posted that string and exited 0. A misleading green that could wave a genuinely-unreviewed PR through — the most dangerous of the three ways #174's review failed.Fixes
False-green (the core fix):
call_claude()now raisesClaudeReviewErroron any failure to get a real review (missing credential, HTTP/credit error, network error, empty/malformed response) — never returns the error as a review.run_reviewer()catches it, posts a clearly-marked "Review could not run — NOT an approval" comment, returnsFalse.main()exits non-zero if any reviewer failed → red/failed check. A green Loxia check now means a real review ran and posted a verdict.Auth (operator directive — prefer Max subscription over metered key):
CLAUDE_CODE_OAUTH_TOKEN(Max subscription, no per-request spend) overANTHROPIC_API_KEY; falls back to the key when the token is absent./v1/messagesisn't confirmed (the swarm's other subscription callers useclaude --print). If it 401s in CI, the fallback is to shell out toclaude --print. Either way, the new failure path makes a bad token a visible red check, not a silent pass. Needs a live-token check in CI to confirm the auth path.Tests
11 pass (added: HTTP-error→raise, empty-response→raise, run_reviewer failure→False+failure-comment, success→True, OAuth-preferred-over-key).
Related
Neotoma tasks: move-CI-off-metered-key + fix-Loxia-false-green. Does NOT fix the other two #174 review failures (Vanellus gate-blocked as a legacy PR; the separate github-actions bot) — those are tracked separately.
🤖 Generated with Claude Code