Skip to content

feat(github): surface structured error identifiers on 4xx responses#512

Merged
piekstra merged 1 commit into
mainfrom
fix/workbench-validate-and-400-detail
Jul 16, 2026
Merged

feat(github): surface structured error identifiers on 4xx responses#512
piekstra merged 1 commit into
mainfrom
fix/workbench-validate-and-400-detail

Conversation

@piekstra

Copy link
Copy Markdown
Contributor

Problem

A rejected GitHub write logs only github: status 400 (response body redacted) — undiagnosable (this blocked root-causing a recurring production 400), even though GitHub's structured payload names exactly which resource.field it rejected and why.

Fix

safeErrorDetail surfaces each error's resource.field.code triple — schema enums, never free text: github: status 400 (PullRequestReview.path.invalid). The top-level message and raw body remain fully redacted; both can echo request content or secrets. Notably, the package's no-leak canary tests caught and rejected a first draft that surfaced message — this final form passes them.

Verification

  • New test: triple surfaced; non-JSON body redacted; message-only JSON redacted; metadata-free JSON redacted.
  • Full ./internal/gitprovider/... suite (incl. secret-canary tests), make lint, go build green.

A rejected GitHub write logged only "github: status 400 (response body
redacted)" — undiagnosable, even though GitHub's structured error payload
names exactly which resource/field it rejected. Surface each error's
resource/field/code triple (schema enums, never free text). The top-level
message and raw body stay fully redacted: both can echo request content or
secret material, as the no-leak canary tests enforce — they caught and
rejected a first draft of this change that surfaced the message field.
@piekstra
piekstra requested a review from piekstra-dev July 16, 2026 19:46

@piekstra-dev piekstra-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR Review

Reviewed commit: 3b5f3f77f980
Profile: reviewer - Posting as: piekstra-dev

Summary

Reviewer Findings
go:implementation-tests 2
go:implementation-tests (2 findings)

Minor - internal/gitprovider/github/http.go:274

The multi-error join ("; ".join over resource.field.code triples) and the 200-char truncation are new, non-trivial logic paths but have no test coverage — the added test in http_test.go only exercises a single-error payload. Two risks go unverified: (1) the join behavior with >1 errors, and (2) detail[:maxDetail] truncates by byte index, which can split a multi-byte rune mid-sequence if resource/field/code ever contain non-ASCII text, producing invalid UTF-8 in the surfaced error string. Add a test case with multiple errors entries to lock in the ; -joined format, and either truncate on a rune boundary (e.g. via a small loop or utf8.RuneCountInString-aware trim) or document why byte truncation is acceptable given the enum-only inputs.

Nits - internal/gitprovider/github/http_test.go:5

The four no-leak scenarios (structured triple, non-JSON, message-only, metadata-free JSON) are asserted sequentially with t.Fatalf in one test function; a failure in an earlier case aborts the rest, hiding whether later canary cases also regressed. Splitting into t.Run subtests (or at least using t.Errorf instead of t.Fatalf) would let all four no-leak assertions report independently, which matters here since these are the canary tests the PR description says caught a prior leak.

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad internal/gitprovider/github/http.go, internal/gitprovider/github/http_test.go unavailable unavailable

0 PR discussion threads considered. 0 summarized; 0 resolved.


Completed in 1m 28s | unavailable | claude-sonnet-5 | cr 0.10.258
Field Value
Model claude-sonnet-5
Reviewers go:implementation-tests
Engine claude_cli · claude-sonnet-5
Reviewed by cr · piekstra-dev
Duration 1m 28s wall · 1m 15s compute
Cost unavailable
Tokens unavailable

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 13s
go:implementation-tests claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 51s
orchestrator-rollup claude-sonnet-5 unavailable unavailable unavailable unavailable unavailable 10s

parts = append(parts, strings.Join(fields, "."))
}
}
const maxDetail = 200

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The multi-error join ("; ".join over resource.field.code triples) and the 200-char truncation are new, non-trivial logic paths but have no test coverage — the added test in http_test.go only exercises a single-error payload. Two risks go unverified: (1) the join behavior with >1 errors, and (2) detail[:maxDetail] truncates by byte index, which can split a multi-byte rune mid-sequence if resource/field/code ever contain non-ASCII text, producing invalid UTF-8 in the surfaced error string. Add a test case with multiple errors entries to lock in the ; -joined format, and either truncate on a rune boundary (e.g. via a small loop or utf8.RuneCountInString-aware trim) or document why byte truncation is acceptable given the enum-only inputs.

Reply inline to this comment.


import "testing"

func TestHTTPStatusErrorSurfacesSafeDetail(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The four no-leak scenarios (structured triple, non-JSON, message-only, metadata-free JSON) are asserted sequentially with t.Fatalf in one test function; a failure in an earlier case aborts the rest, hiding whether later canary cases also regressed. Splitting into t.Run subtests (or at least using t.Errorf instead of t.Fatalf) would let all four no-leak assertions report independently, which matters here since these are the canary tests the PR description says caught a prior leak.

Reply inline to this comment.

@piekstra
piekstra merged commit 0e9821e into main Jul 16, 2026
10 checks passed
@piekstra
piekstra deleted the fix/workbench-validate-and-400-detail branch July 16, 2026 19:49
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.

2 participants