Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@

Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (no `.`/`..`); a lone `--require-hashes` directive is not trust evidence. See [`docs/doctoring/hourly-nvidia-nim-autofix.md`](docs/doctoring/hourly-nvidia-nim-autofix.md).
Conflict-scope roots fail closed when the immediate parent directory is a symbolic link.
GraphQL `invalid UTF-8 string` and query-cost overruns are transient; fall back to REST. Do not treat review/Checks wait as a blocker.
7 changes: 7 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ flowchart LR
Products -->|"standalone or as module"| Buyer
```

## Merge-scheduler GraphQL fallback

`TRANSIENT_GITHUB_API_ERRORS` includes `invalid UTF-8 string` and
`Resource limits for this query exceeded`. Those failures retry, then
fall back to REST so Unicode refs and large queues do not abort the
org scan. Authorization and schema errors stay fail-closed.

## Hourly NVIDIA NIM repair gate

```mermaid
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Treat GraphQL `invalid UTF-8 string` and `Resource limits for this query exceeded` as transient merge-scheduler failures so Unicode branch names and large org queues fall back to REST instead of stalling.
- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context.
- Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367).
- Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics.
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ repeatable compile command.
without running the test suite will break CI.
- **100% coverage and 100% docstrings on `scripts/ci/`** are hard gates, not aspirations. New helper
code needs matching tests and docstrings.
- **GraphQL `invalid UTF-8 string` and query-cost overruns** are transient for the merge
scheduler. Fall back to REST; do not abort the org queue.
- **`pull_request_target` trust boundary.** The required review workflows run the *base branch's*
trusted scripts. A PR that edits the trusted review workflows can fail its own checks until the
base branch catches up; a same-head manual `workflow_dispatch` Strix run may supply review evidence
Expand Down
29 changes: 29 additions & 0 deletions docs/doctoring/scheduler-utf8-graphql-fallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Scheduler UTF-8 GraphQL fallback

## Incident and buyer impact

The merge scheduler aborted while listing pull requests because `gh api
graphql` returned `invalid UTF-8 string`. That marker was not treated as
transient, so the existing REST fallback never ran. Unicode branch names
and comment bodies then blocked the entire org queue.

## Decision

Treat `invalid UTF-8 string` and `Resource limits for this query
exceeded` as transport/capacity failures, not schema or authorization
errors. Retry and fall back to REST. Do not treat GraphQL field errors
or `Resource not accessible by integration` as transient. This keeps
operational Unicode content and the full open-PR queue available
instead of masking or dropping them.

## References

Yergeau, F. (2003). *UTF-8, a transformation format of ISO 10646*
(RFC 3629). Internet Engineering Task Force.
https://doi.org/10.17487/RFC3629

GitHub. (2025). *Using the GitHub GraphQL API*.
https://docs.github.com/en/graphql

GitHub. (2025). *Rate limits and node limits for the GraphQL API*.
https://docs.github.com/en/graphql/overview/rate-limits-and-node-limits-for-the-graphql-api
2 changes: 2 additions & 0 deletions scripts/ci/pr_review_merge_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,8 @@ def repository_dispatch_wait_reason(repo: str, workflow: str) -> str | None:
"stream error",
"temporary failure",
"timeout",
"invalid UTF-8 string",
"Resource limits for this query exceeded",
"unexpected end of JSON input",
"unexpected EOF",
"received from peer",
Expand Down
52 changes: 52 additions & 0 deletions tests/test_pr_review_merge_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,58 @@ def fail_graphql(*args, **kwargs):
assert sched.fetch_pr("owner/repo", 1) == [{"repo": "owner/repo", "number": 1}]


def test_graphql_utf8_errors_fall_back_to_rest(monkeypatch):
"""Unicode GraphQL transport failures must not stall the org queue."""

def fail_graphql(*args, **kwargs):
raise RuntimeError("Command failed (1): gh api graphql\ngh: invalid UTF-8 string")

monkeypatch.setattr(sched, "gh_graphql", fail_graphql)
monkeypatch.setattr(sched, "fetch_open_prs_rest", lambda repo, max_prs: [{"repo": repo, "max": max_prs}])
monkeypatch.setattr(sched, "fetch_pr_rest", lambda repo, number: [{"repo": repo, "number": number}])

assert sched.is_transient_github_api_error(RuntimeError("invalid UTF-8 string"))
assert sched.fetch_open_prs("owner/repo", 1) == [{"repo": "owner/repo", "max": 1}]
assert sched.fetch_pr("owner/repo", 1) == [{"repo": "owner/repo", "number": 1}]


def test_graphql_resource_limit_falls_back_to_rest(monkeypatch):
"""A 58-PR GraphQL list that exceeds GitHub query cost uses REST instead of aborting."""

def fail_graphql(*args, **kwargs):
raise RuntimeError(
"GraphQL: Resource limits for this query exceeded. "
"(repository.pullRequests.nodes.0.url), "
"Resource limits for this query exceeded. "
"(repository.pullRequests.nodes.1.number)"
)

monkeypatch.setattr(sched, "gh_graphql", fail_graphql)
monkeypatch.setattr(
sched,
"fetch_open_prs_rest",
lambda repo, max_prs: [{"repo": repo, "max": max_prs, "via": "rest"}],
)
monkeypatch.setattr(
sched,
"fetch_pr_rest",
lambda repo, number: [{"repo": repo, "number": number, "via": "rest"}],
)

observed = RuntimeError(
"GraphQL: Resource limits for this query exceeded. "
"(repository.pullRequests.nodes.0.url)"
)
assert sched.is_transient_github_api_error(observed)
assert not sched.github_resource_inaccessible(observed)
assert sched.fetch_open_prs("ContextualWisdomLab/.github", 58) == [
{"repo": "ContextualWisdomLab/.github", "max": 58, "via": "rest"}
]
assert sched.fetch_pr("ContextualWisdomLab/.github", 934) == [
{"repo": "ContextualWisdomLab/.github", "number": 934, "via": "rest"}
]


def test_graphql_read_errors_do_not_fall_back_for_schema_errors(monkeypatch):
def fail_graphql(*args, **kwargs):
raise RuntimeError("gh: Field 'unknown' doesn't exist on type 'PullRequest'")
Expand Down
Loading