Skip to content

fix(contributor-check): report an account GitHub search refuses as UNKNOWN, and name the path on API errors - #57

Merged
imran-siddique merged 2 commits into
mainfrom
fix/contributor-check-search-refusal
Oct 7, 2026
Merged

imran-siddique merged 2 commits into
mainfrom
fix/contributor-check-search-refusal

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

Closes #56.

The 422 is the search API declining an author: query for the PR's author: "The listed users cannot be searched either because the users do not exist or you do not have permission to view the users." It does this for the authors of both failing runs (#49, #53) under a maintainer token as well, while a control account searches normally. Under the account's own token it returns 200, which is why the local trace in #56 did not reproduce it.

Five new tests, all five failing against main. Run end to end against the live API, the author of #49 now returns UNKNOWN instead of exiting 1, and a control account still returns LOW.

🤖 Generated with Claude Code

…KNOWN

The search API answers 422 "The listed users cannot be searched" for some
accounts, which crashed the check. Report those as UNKNOWN with a
search_unavailable signal, and name the request path on every API failure
line.

Closes #56.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
@Mayur021

Mayur021 commented Oct 5, 2026

Copy link
Copy Markdown
Member

That explains the trace cleanly. I had narrowed it to the token identity rather than the query shape and then had nowhere further to go, because the refusal names no path and the local run was authenticated as the account being searched. Reading #57, the discrimination is the part I would have got wrong: a generic 422 still raises, so the refusal is handled without the catch widening to every validation failure.

One thing worth knowing rather than changing. The refusal is a property of the account, not a transient fault, so an account that search refuses lands UNKNOWN every time rather than once. If that property is something the account holder can set or trigger, the check has a self-service exemption, and the person most motivated to find it is the one the check exists for. If it is a GitHub-side state the account does not control, it is just a small unscoreable population and the signal is doing its job.

I do not know which, and the answer changes whether search_unavailable is a diagnostic or a gap worth its own issue. Either way it is better named than crashed.

@imran-siddique

Copy link
Copy Markdown
Member Author

@Mayur021 it is not an exemption either way. The action orders UNKNOWN above MEDIUM (RISK_ORDER in contributor_check_action.py), so a refused account is labelled needs-review:UNKNOWN and still goes to a maintainer. I know of no account setting that hides a user from issue search, which points at GitHub-side state. The ordering makes that question matter less: being unscoreable costs the account a review rather than saving it one.

@Mayur021

Mayur021 commented Oct 5, 2026

Copy link
Copy Markdown
Member

The ordering settles it, thanks. I had not read that far down the action.

One thing I noticed while checking, and I would not hold the PR for it. The UNKNOWN is carried entirely by the profile check's explicit report.risk = "UNKNOWN". The other probe that runs by default swallows the same 422: credential_audit.py has if exc.code in (404, 422): return None in its _api, and _search turns that into []. So for a refused account find_merges comes back empty, audit() takes the if not report.merges branch and sets NONE, and _run_check normalises NONE to LOW. cluster_detect.py carries the identical 422 branch.

Because _aggregate_risk is a max, one UNKNOWN is enough and the overall label is right today. The part worth knowing is that the other probes are not independent corroboration for this account class. They are reading the same refused search as an absence of history, which is the one reading that scores clean. Same treatment in a follow-up, if you agree it is the same bug.

…ntial and cluster probes

Both probes turned every 422 into None, so an account GitHub search
refuses read as having no merge or issue history and scored NONE. The
overall label was still UNKNOWN through the profile check, but the
credential row reported a clean result it never established.

A 422 whose body says the user cannot be searched now raises
SearchUnavailable in each probe's _api, and the probe reports UNKNOWN.
Any other 422 is read as before. Both files leave the unmodified set,
so their checksums move out of vendor-integrity and into the README's
provenance note. Raised by @Mayur021 on #57.

Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@imran-siddique

Copy link
Copy Markdown
Member Author

@Mayur021 confirmed, and it is in this PR now (f70a941). credential_audit.py and cluster_detect.py each recognise the refusal in their own _api and report UNKNOWN; any other 422 reads as before. test_probe_search_unavailable.py fails on both probes without the change and passes with it. Both files leave the byte-identical set, so their upstream checksums moved out of vendor-integrity and into the provenance note.

You were right that the credential row was reporting a clean history it had never read.

@Mayur021

Mayur021 commented Oct 6, 2026

Copy link
Copy Markdown
Member

Checked it. The refusal test lands before the 404-and-422 catch-all in both probes, so the swallow can no longer reach a refused search, and the cluster report carries the flag into risk_level rather than inferring it from an edge count that was never established.

The part I would not have thought to ask for is keeping both upstream digests in the README as prose instead of dropping the rows. The fork boundary stays auditable that way: someone can still establish what the bytes were before the change, which a deleted row would have cost permanently.

Nothing further from me on this one.

@Qiang-Xu Qiang-Xu 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.

Looks good to me in general. Do we know the root cause of the 422 error?

@imran-siddique
imran-siddique merged commit 7eacd9e into main Oct 7, 2026
5 of 8 checks passed
@imran-siddique
imran-siddique deleted the fix/contributor-check-search-refusal branch October 7, 2026 00:06
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.

[Bug] _api raises without naming the request path, so the 422 in Contributor Reputation Check cannot be located

3 participants