Skip to content

Report the gh error, not just an exit code - #50

Merged
gavinbee merged 1 commit into
mainfrom
49-surface-gh-errors
Sep 5, 2026
Merged

Report the gh error, not just an exit code#50
gavinbee merged 1 commit into
mainfrom
49-surface-gh-errors

Conversation

@gavinbee

@gavinbee gavinbee commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Closes #49

What & why

The reconciler run on 2026-09-05 reported this and nothing else:

  FAIL labels: could not list (exit 1)
  FAIL labels: could not list (exit 1)

The cause was HTTP 403: the swimblocks-reconciler App had no issues permission, and labels sit under issues for GitHub Apps. gh had said so on stderr, and the script discarded it — so establishing it meant reading the workflow logs, noticing the failures were confined to private repos, and then querying the installation to compare the App's declared permissions against the ones actually granted.

These calls run with capture_output=True, so the message was already sitting on CalledProcessError.stderr.

gh_error() appends its last line — the gh: … (HTTP 403) summary rather than the noise above it — and falls back to the exit code when stderr is empty. Applied to the label paths and the ruleset apply.

Not applied to the private-repo branch-protection SKIP. An unsupported plan is the documented expected outcome there and the message already explains itself; appending a raw gh error would make a normal condition read like a fault.

A second bug, found while writing it

main() discarded apply_labels' return value:

apply_labels(repo, labels_block)
if not verify_labels(repo, labels_block) and repo not in failures:

Two consequences. The failure printed twice per repo, because apply_labels and verify_labels each call list_labels and each reported. And a genuine apply failure could be dropped whenever verification happened to pass. It now verifies only if applying got that far.

Testing

ruff check . clean. pytest -q26 passed, up from 21; five new tests on gh_error covering the real 403 text, multi-line stderr, empty stderr, None stderr, and whitespace-only stderr.

Proved against a live gh failure rather than only mocks:

before:  FAIL labels: could not list (exit 1)
after:   FAIL labels: could not list: gh: Not Found (HTTP 404)

Also confirmed the underlying incident is resolved and the reconciler is green end to end — issues: write accepted on the installation, and a deliberately deleted label recreated on a private repo:

=== swimblocks/rems-sync-apps-script ===
  OK  label 'no-issue': created
  OK  label 'no-issue': present

Which is the small irony of this PR: the run that would have shown off the better message came back clean.

Checklist

  • Linked the issue this PR closes
  • Tests added/updated and passing
  • ruff check . clean (Python repos)
  • Docs/README updated if user-facing behaviour changed — n/a, operator output only

🤖 Generated with Claude Code

The 2026-09-05 reconciler run said only:

  FAIL labels: could not list (exit 1)
  FAIL labels: could not list (exit 1)

The cause was HTTP 403 — the swimblocks-reconciler App had no issues
permission, and labels sit under issues for GitHub Apps. gh had said so on
stderr, and the script threw it away, so establishing that took reading the
workflow logs, noticing the failures were confined to private repos, and
querying the installation to compare declared against granted permissions.
The output could have handed it over.

These calls run with capture_output=True, so the message was already on
CalledProcessError.stderr. gh_error() appends its last line — the
"gh: ... (HTTP 403)" summary rather than the noise above it — and falls
back to the exit code when stderr is empty. Applied to the label paths and
to the ruleset apply. The private-repo branch-protection SKIP is left
alone: an unsupported plan is the documented expected outcome and that
message already explains itself.

The failure also printed twice per repo, because apply_labels and
verify_labels each call list_labels and each reported. main() discarded
apply_labels' return value entirely, so verifying ran even when applying
had already failed. It now verifies only if applying got that far, which
fixes the duplicate and stops a real failure being dropped.

Against a live failure:

  before:  FAIL labels: could not list (exit 1)
  after:   FAIL labels: could not list: gh: Not Found (HTTP 404)

Closes #49

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgLE8cg9huy2EZnRoXhmwp
@gavinbee
gavinbee merged commit 5bc0998 into main Sep 5, 2026
1 check passed
@gavinbee
gavinbee deleted the 49-surface-gh-errors branch September 5, 2026 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

apply-settings.py discards the gh error message and reports only an exit code

1 participant