Skip to content

fix: retry transient GraphQL errors beyond HTTP 502 - #2915

Open
CodeByPeace wants to merge 2 commits into
googleapis:mainfrom
CodeByPeace:fix/retry-transient-graphql-errors
Open

CodeByPeace wants to merge 2 commits into
googleapis:mainfrom
CodeByPeace:fix/retry-transient-graphql-errors

Conversation

@CodeByPeace

@CodeByPeace CodeByPeace commented Oct 4, 2026 •

Copy link
Copy Markdown

Fixes #2905

What was wrong: Both GraphQL retry loops, in github.ts and github-api.ts, only retried when the status was 502. A GraphQL error returned with HTTP 200 has no status, so it was thrown after one request. HTTP 500, 503 and 504 also stopped after one request.

What changed: One shared check, isTransientGraphqlError in github-api.ts, is used by both loops. It retries HTTP 500, 502, 503 and 504. For HTTP 200 responses it retries only when every entry in errors starts with one of the two transient messages from the issue ("Something went wrong while executing your query", "We couldn't respond to your request in time"; straight or curly apostrophe). A response that mixes a transient error with another error, an empty errors array, and a permanent error that only quotes a transient phrase are all thrown at once, as before. Non-object entries in errors no longer throw a TypeError inside the retry handler. A reviewer comment on the issue pointed out the mixed-response and substring problems; I checked both against the code and they were real.

How I tested: I wrote the HTTP 200 test first and watched it fail on the old code. For the second commit I wrote 6 tests first; 3 of them failed on the old code (mixed response, quoted phrase, a null entry in errors, which threw a TypeError). After the fix, all pass. I ran npm test (1232 passing) and npm run fix (0 errors). I ran these myself.

Limits: The HTTP status branch is unchanged: a 5xx status is retried without looking at errors. Neither retry loop reads Retry-After or rate-limit headers, before or after this change, and I did not add that. The batch size halving that already ran on 502 now runs for the other transient errors too. I did not add a loop test for the copy in github-api.ts, only tests of the check it uses. CI has not run yet.

Left alone: rate limit, auth and query errors, the retry count and the sleep times. There are no mutations in src, so nothing that creates a release or PR is retried.

AI use: written with Claude Sonnet 5.5. I ran and checked every command and test myself.

  • Make sure to open an issue as a bug/issue before writing your code. The issue already existed. I commented on it before opening this PR, so the design was not agreed first.
  • Ensure the tests and linter pass. Run locally, CI has not run yet.
  • Code coverage does not decrease. I did not compare it with main.
  • Appropriate docs were updated. No docs needed changing.

Both GraphQL retry loops only retried on HTTP 502. A GraphQL error returned with HTTP 200 has no status, so it was thrown at once. Add one shared check that also treats HTTP 500, 503 and 504 and the two transient error messages from the issue as retryable. Other errors are handled as before.

Fixes googleapis#2905
@product-auto-label product-auto-label Bot added the size: m Pull request size is medium. label Oct 4, 2026
@google-cla

google-cla Bot commented Oct 4, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@TBaltensperger

Copy link
Copy Markdown

Identity: TBaltensperger @ nuitio/hq

Preserve permanent-error refusal in the shared retry classifier.

Topic GraphQL read retries · From 2905's concrete source feedback and complete patch · Kind source feedback · Needs @CodeByPeace: incorporate the refusal controls · Date2026-10-25

Carrying the downstream findings onto the actual draft PR so they are visible in its review. The current head is still 3fe617edc75bcc12daa727fcf76faec2cbe95189, the same source examined in that issue reply. The shared policy is a useful direction; the current predicate can retry permanent failures.

At github-api.ts:165–172:

  • errors.some(...) accepts a response containing both a transient error and Resource not accessible by integration. Require every GraphQL error to be recognized as transient, including its error type.
  • Substring matching accepts a query-validation message that merely quotes transient wording. Match a recognized transient prefix instead.
  • A transient numeric HTTP status returns before checking the error array or rate-limit information. A503 response with a forbidden field, or a response carrying Retry-After/rate-limit exhaustion, must preserve the permanent/rate-limit path.

For example, the current predicate accepts this mixed response when status is absent:

{"errors":[{"message":"Something went wrong while executing your query."},{"message":"Resource not accessible by integration"}]}

The complete transferable patch implements those refusals and exercises both actual public client loops, while retaining backoff, adaptive page size, exhaustion and write refusal. Its12,453bytes/SHA25600f89be86253d75190b957e5d2c722d71b057dc21bf73d0b3614c1e3816644e9 and compile/58targeted-test evidence are bound to maintained mainc589293ca38aee267b329f9bf9b26c9d6516a687. They are local evidence for that proposed patch; this draft head keeps its own validation obligation. No new full suite or upstream CI was run for this comment. Please incorporate that patch or carry the same rejection cases into this implementation and both client-loop tests.

The newer cla/google check111413361386 is successful; the initial failed CLA bot comment is historical. The draft's requested maintainer design/admission answer remains with maintainers. This downstream feedback grants no upstream approval or merge authority.

Assignee: @CodeByPeace: address the source refusal controls and return the updated head; release-please maintainers: own the design/admission answer, independent CI/review and eventual merge/publication. The downstream HQ contributor will address concrete patch findings and follow the published dependency repair.

@TBaltensperger

Copy link
Copy Markdown

Identity: TBaltensperger @ nuitio/hq

Evidence appendix for an exact-head correction to this draft. The complete applicable two-file patch follows separately. These are author-local runtime records; normal type checking and upstream CI remain unverified after three unchanged512MiB RSS stops. No CI was dispatched, rerun or cancelled.

69 targeted runtime tests pass; nine semantic mutants are caught in27candidate cells. The maintained matrix remains exit-status-only. Separately, the calibrated Mocha event reporter observed41assertion failures/zero runtime errors and distinguished real assertions from intentional runtime errors and a spoofed name.

The tested uncommitted3fe source bytes independently match local proposal21fa636c; no upstream push exists. Common fields apply to every framework observation; expanding them exactly reconstructs all36records. Source-frame keys bind canonical source/generated-JS dictionaries. Null native/loaded/module authority and all qualification gaps remain.

Complete safe definitions, tool sources, results and failed history — SHA2561263f7c94424050c0951eede6ffc374ff76eb67bbfd1a2973a0d0b6610c2a1e1
{"kind":"qualified author-local exact-draft source and runtime proposal; type-check/CI unverified","source":{"base":"3fe617edc75bcc12daa727fcf76faec2cbe95189","local_proposal_commit":"21fa636c6f819f3c732fe6907d19fb8e8abcaf6c","tree":"0756aa5e5c5f628dd3f671673d019098d9f2ce1c","sole_parent_verified":true,"raw_Git_self_hash_verified":true,"local_only_no_push":true,"clean_worktree":true,"patch_sha256":"0967c238021514569b40f1e47a9ff8d64d6d4d41a975a19d8d018c167599cd67","patch_bytes":11211,"alternate_index_patch_applies_to_exact_base_tree":true,"applied_tree_equals_proposal":true,"real_index_worktree_HEAD_unchanged_during_apply_check":true,"changed_paths":["src/github-api.ts","test/github.ts"]},"final_source_file_sha256":{"src/github-api.ts":"db78176ba580a54ee42f2fbacc719da517a97dc3f04c4b1f49a3b718080ab4b2","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"dependency_files":{"package.json":"e6d66167e2a4caf170b7b1d441dc9a2aaba126e109be421b265fbb202db2788d","package-lock.json":"f1fa2ea6d8f52859568ecba858d8212e6dbdce7beaddce9641f513bd4cbffe86"},"runtime_artifact":{"kind":"transpile-only runtime artifact; typed build remains unverified","full_manifest_sha256":"5e228fca5d4d1d346625d643b99a293fc01e8889da9c5e11134be0f92272f4bc","full_manifest_file_count":153,"changed_source_and_outputs":["src/github-api.ts","test/github.ts"],"all_other_151_source_and_runtime_files_equal_original":true,"qualification":"Local filesystem observation; full retained manifest hash is not public/loaded/native dependency attestation.","compiler":{"version":"4.9.4","typescript_module_sha256":"48a135114996ca37a779a51802f78612e1b028bf26df8364927f1df674e86f15"}},"runtime_tests":{"baseline_stats":{"suites":7,"tests":23,"passes":23,"pending":0,"failures":0,"start":"2026-10-25T04:58:46.295Z","end":"2026-10-25T04:58:46.640Z","duration":345},"final_stats":{"suites":7,"tests":69,"passes":69,"pending":0,"failures":0,"start":"2026-10-25T05:22:09.669Z","end":"2026-10-25T05:22:10.166Z","duration":497},"existing_18_owning_cases_retained":true,"existing_5_history_write_refusal_controls_retained":true,"new_public_client_cases":46,"new_API_cases":26,"new_GitHub_cases":20,"new_matrix_GitHub_cases_omitted_because_existing_tests_remain":["HTTP 500","HTTP 502","HTTP 503","HTTP 504","internal error","ordinary query validation"],"retired_tests":[]},"checks":{"install":{"command":["npm","ci","--ignore-scripts","--no-audit","--no-fund"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T04:44:11.153505+00:00","ended_at":"2026-10-25T04:44:22.703328+00:00","elapsed_seconds":11.552035384927876,"outcome":"passed","exit_code":0,"owned_survivor_count":0},"start-compile":{"command":["npm","run","compile"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T04:45:12.063115+00:00","ended_at":"2026-10-25T04:45:15.410861+00:00","elapsed_seconds":3.3477809299947694,"outcome":"stopped_rss_cap","exit_code":-15,"owned_survivor_count":0},"target-start-compile":{"command":["npm","run","compile","--","--project","/tmp/hq372-2915-target-tsconfig.json"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T04:49:35.900534+00:00","ended_at":"2026-10-25T04:49:40.959286+00:00","elapsed_seconds":5.058790266979486,"outcome":"stopped_rss_cap","exit_code":-15,"owned_survivor_count":0},"direct-target-start-compile":{"command":["node","node_modules/typescript/bin/tsc","--project","/tmp/hq372-2915-target-tsconfig.json"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T04:51:38.879378+00:00","ended_at":"2026-10-25T04:51:43.020353+00:00","elapsed_seconds":4.141010480001569,"outcome":"stopped_rss_cap","exit_code":-15,"owned_survivor_count":0},"runtime-build-old":{"command":["python","/tmp/hq372-2915-transpile-runtime.py"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T04:57:44.179100+00:00","ended_at":"2026-10-25T04:58:00.043940+00:00","elapsed_seconds":15.867095624911599,"outcome":"passed","exit_code":0,"owned_survivor_count":0},"real-boundary-red":{"command":["node","/tmp/hq372-2915-real-boundary-probe.cjs"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T04:58:45.237854+00:00","ended_at":"2026-10-25T04:58:49.856412+00:00","elapsed_seconds":4.618596037966199,"outcome":"failed","exit_code":1,"owned_survivor_count":0},"real-boundary-green":{"command":["node","/tmp/hq372-2915-real-boundary-probe.cjs"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:00:08.298122+00:00","ended_at":"2026-10-25T05:00:08.833752+00:00","elapsed_seconds":0.5356703139841557,"outcome":"passed","exit_code":0,"owned_survivor_count":0},"final-frozen-runtime-tests":{"command":["node_modules/.bin/mocha","--timeout","5000","build/test/github.js","--grep","graphqlRequest backoff|isTransientGraphqlError|paginates through commits|mergeCommitIterator|createRelease should raise|commentOnIssue propagates","--reporter","json"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:22:08.659644+00:00","ended_at":"2026-10-25T05:22:10.466561+00:00","elapsed_seconds":1.8069542240118608,"outcome":"passed","exit_code":0,"owned_survivor_count":0},"final-eslint":{"command":["node_modules/.bin/eslint","src/github-api.ts","test/github.ts"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:21:23.436817+00:00","ended_at":"2026-10-25T05:21:26.530682+00:00","elapsed_seconds":3.0938981409417465,"outcome":"passed","exit_code":0,"owned_survivor_count":0},"final-prettier":{"command":["node_modules/.bin/prettier","--check","src/github-api.ts","test/github.ts"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:21:23.441924+00:00","ended_at":"2026-10-25T05:21:24.741631+00:00","elapsed_seconds":1.2997452270938084,"outcome":"passed","exit_code":0,"owned_survivor_count":0},"final-runtime-rebuild":{"command":["node","/tmp/hq372-2915-transpile-batch.cjs","/workspace/.worktrees/hq372-upstream2915-correction","src/github-api.ts","src/github.ts","test/github.ts"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:21:23.443524+00:00","ended_at":"2026-10-25T05:21:24.745420+00:00","elapsed_seconds":1.3019333820557222,"outcome":"passed","exit_code":0,"owned_survivor_count":0},"reporter-calibration":{"command":["python","/tmp/hq372-2915-calibrate-reporter.py"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:13:56.111132+00:00","ended_at":"2026-10-25T05:13:56.926323+00:00","elapsed_seconds":0.8152320289518684,"outcome":"passed","exit_code":0,"owned_survivor_count":0},"missing-compiled-test-after-interrupted-build":{"command":["node_modules/.bin/mocha","--timeout","5000","build/test/github.js","--grep","graphqlRequest backoff|isTransientGraphqlError|paginates through commits|mergeCommitIterator|createRelease should raise|commentOnIssue propagates","--reporter","json"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T04:47:00.098413+00:00","ended_at":"2026-10-25T04:47:00.628320+00:00","elapsed_seconds":0.5299356230534613,"outcome":"failed","exit_code":1,"owned_survivor_count":0},"initial-invalid-mutation-observer":{"command":["python","/home/node/.codex/plugins/cache/hq/hq/0.20.0/skills/hq-create-test/scripts/kill_matrix.py","/tmp/hq372-2915-runtime-matrix-0-input.json"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:08:38.168464+00:00","ended_at":"2026-10-25T05:09:11.924181+00:00","elapsed_seconds":33.759979694965295,"outcome":"passed","exit_code":0,"owned_survivor_count":0},"initial-format-check":{"command":["node_modules/.bin/prettier","--check","src/github-api.ts","test/github.ts"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:03:08.688168+00:00","ended_at":"2026-10-25T05:03:11.110431+00:00","elapsed_seconds":2.4242252591066062,"outcome":"failed","exit_code":1,"owned_survivor_count":0}},"focused_typecheck_config":{"extends":"/workspace/.worktrees/hq372-upstream2915-correction/tsconfig.json","include":["/workspace/.worktrees/hq372-upstream2915-correction/typings/**/*.d.ts","/workspace/.worktrees/hq372-upstream2915-correction/src/github-api.ts","/workspace/.worktrees/hq372-upstream2915-correction/src/github.ts","/workspace/.worktrees/hq372-upstream2915-correction/test/github.ts"]},"typecheck_options_binding":{"compiler_version":"4.9.4","compiler_options_equal_except_config_path":true,"original_root_count":281,"selected_root_files":["typings/config-chain.d.ts","typings/iarna__toml.d.ts","src/github-api.ts","src/github.ts","test/github.ts"]},"mutations":{"summary":{"mutants":9,"candidate_cells":27,"caught":15,"survived":12,"error":0,"observed_assertion_failures":41,"observed_runtime_errors":0},"packets":[{"safe_task":{"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","command":"python /tmp/hq372-2915-runtime-test-driver.py {test}","tests":["pure","api","github"],"mutations":[{"id":"all-errors","file":"src/github-api.ts","line":193,"before":"graphqlErrors.every","after":"graphqlErrors.some"},{"id":"message-prefix","file":"src/github-api.ts","line":200,"before":"startsWith(text)","after":"includes(text)"},{"id":"error-type","file":"src/github-api.ts","line":197,"before":"(type === undefined || type === 'INTERNAL')","after":"true"}],"timeout_seconds":30,"fail_exit_codes":[1]},"maintained_result":{"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","command":"python /tmp/hq372-2915-runtime-test-driver.py {test}","timeout_seconds":30,"recovered":null,"baseline":{"pure":{"result":"pass","exit_code":0,"seconds":3.574,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"result":"pass","exit_code":0,"seconds":3.831,"line":"{\"candidate\": \"api\", \"outcome\": \"passed\", \"tests_run\": 26, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"result":"pass","exit_code":0,"seconds":3.765,"line":"{\"candidate\": \"github\", \"outcome\": \"passed\", \"tests_run\": 20, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"matrix":[{"mutation":"all-errors","file":"src/github-api.ts","line":193,"before":"graphqlErrors.every","after":"graphqlErrors.some","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":3.501,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"caught","exit_code":1,"seconds":3.368,"line":"{\"candidate\": \"api\", \"outcome\": \"assertion-failure\", \"tests_run\": 26, \"assertion_failures\": 7, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"caught","exit_code":1,"seconds":4.092,"line":"{\"candidate\": \"github\", \"outcome\": \"assertion-failure\", \"tests_run\": 20, \"assertion_failures\": 7, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":["api","github"]},{"mutation":"message-prefix","file":"src/github-api.ts","line":200,"before":"startsWith(text)","after":"includes(text)","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":3.975,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"caught","exit_code":1,"seconds":3.461,"line":"{\"candidate\": \"api\", \"outcome\": \"assertion-failure\", \"tests_run\": 26, \"assertion_failures\": 1, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"caught","exit_code":1,"seconds":4.352,"line":"{\"candidate\": \"github\", \"outcome\": \"assertion-failure\", \"tests_run\": 20, \"assertion_failures\": 1, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":["api","github"]},{"mutation":"error-type","file":"src/github-api.ts","line":197,"before":"(type === undefined || type === 'INTERNAL')","after":"true","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":3.401,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"caught","exit_code":1,"seconds":3.429,"line":"{\"candidate\": \"api\", \"outcome\": \"assertion-failure\", \"tests_run\": 26, \"assertion_failures\": 1, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"caught","exit_code":1,"seconds":3.292,"line":"{\"candidate\": \"github\", \"outcome\": \"assertion-failure\", \"tests_run\": 20, \"assertion_failures\": 1, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":["api","github"]}],"tests":{"pure":{"catches":[],"complete":true,"subsumed_by":["api","github"],"only_it_catches":[]},"api":{"catches":["all-errors","error-type","message-prefix"],"complete":true,"subsumed_by":["github"],"only_it_catches":[]},"github":{"catches":["all-errors","error-type","message-prefix"],"complete":true,"subsumed_by":["api"],"only_it_catches":[]}},"uncaught":[],"coverage":"full","verification":{"files":[{"file":"src/github-api.ts","byte_identical":true}],"git_status_unchanged":true}},"guard":{"command":["python","/home/node/.codex/plugins/cache/hq/hq/0.20.0/skills/hq-create-test/scripts/kill_matrix.py","/tmp/hq372-2915-runtime-matrix-0-input.json"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:14:41.614178+00:00","ended_at":"2026-10-25T05:15:26.121036+00:00","elapsed_seconds":44.51272280002013,"outcome":"passed","exit_code":0,"owned_survivor_count":0}},{"safe_task":{"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","command":"python /tmp/hq372-2915-runtime-test-driver.py {test}","tests":["pure","api","github"],"mutations":[{"id":"retry-after","file":"src/github-api.ts","line":175,"before":"responseHeaders?.['retry-after'] !== undefined","after":"false"},{"id":"rate-exhaustion","file":"src/github-api.ts","line":176,"before":"responseHeaders?.['x-ratelimit-remaining'] === '0'","after":"false"},{"id":"permanent-status","file":"src/github-api.ts","line":182,"before":"status !== 200","after":"false"}],"timeout_seconds":30,"fail_exit_codes":[1]},"maintained_result":{"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","command":"python /tmp/hq372-2915-runtime-test-driver.py {test}","timeout_seconds":30,"recovered":null,"baseline":{"pure":{"result":"pass","exit_code":0,"seconds":3.856,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"result":"pass","exit_code":0,"seconds":3.551,"line":"{\"candidate\": \"api\", \"outcome\": \"passed\", \"tests_run\": 26, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"result":"pass","exit_code":0,"seconds":3.449,"line":"{\"candidate\": \"github\", \"outcome\": \"passed\", \"tests_run\": 20, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"matrix":[{"mutation":"retry-after","file":"src/github-api.ts","line":175,"before":"responseHeaders?.['retry-after'] !== undefined","after":"false","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":3.341,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"caught","exit_code":1,"seconds":4.042,"line":"{\"candidate\": \"api\", \"outcome\": \"assertion-failure\", \"tests_run\": 26, \"assertion_failures\": 1, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"caught","exit_code":1,"seconds":3.619,"line":"{\"candidate\": \"github\", \"outcome\": \"assertion-failure\", \"tests_run\": 20, \"assertion_failures\": 1, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":["api","github"]},{"mutation":"rate-exhaustion","file":"src/github-api.ts","line":176,"before":"responseHeaders?.['x-ratelimit-remaining'] === '0'","after":"false","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":3.846,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"caught","exit_code":1,"seconds":3.657,"line":"{\"candidate\": \"api\", \"outcome\": \"assertion-failure\", \"tests_run\": 26, \"assertion_failures\": 1, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"caught","exit_code":1,"seconds":3.208,"line":"{\"candidate\": \"github\", \"outcome\": \"assertion-failure\", \"tests_run\": 20, \"assertion_failures\": 1, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":["api","github"]},{"mutation":"permanent-status","file":"src/github-api.ts","line":182,"before":"status !== 200","after":"false","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":3.448,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"caught","exit_code":1,"seconds":3.223,"line":"{\"candidate\": \"api\", \"outcome\": \"assertion-failure\", \"tests_run\": 26, \"assertion_failures\": 4, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"caught","exit_code":1,"seconds":3.291,"line":"{\"candidate\": \"github\", \"outcome\": \"assertion-failure\", \"tests_run\": 20, \"assertion_failures\": 4, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":["api","github"]}],"tests":{"pure":{"catches":[],"complete":true,"subsumed_by":["api","github"],"only_it_catches":[]},"api":{"catches":["permanent-status","rate-exhaustion","retry-after"],"complete":true,"subsumed_by":["github"],"only_it_catches":[]},"github":{"catches":["permanent-status","rate-exhaustion","retry-after"],"complete":true,"subsumed_by":["api"],"only_it_catches":[]}},"uncaught":[],"coverage":"full","verification":{"files":[{"file":"src/github-api.ts","byte_identical":true}],"git_status_unchanged":true}},"guard":{"command":["python","/home/node/.codex/plugins/cache/hq/hq/0.20.0/skills/hq-create-test/scripts/kill_matrix.py","/tmp/hq372-2915-runtime-matrix-1-input.json"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:16:25.058295+00:00","ended_at":"2026-10-25T05:17:07.899678+00:00","elapsed_seconds":42.84450711798854,"outcome":"passed","exit_code":0,"owned_survivor_count":0}},{"safe_task":{"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","command":"python /tmp/hq372-2915-runtime-test-driver.py {test}","tests":["pure","api","github"],"mutations":[{"id":"empty-errors","file":"src/github-api.ts","line":192,"before":"graphqlErrors.length > 0","after":"true"},{"id":"api-retry-caller","file":"src/github-api.ts","line":322,"before":"!isTransientGraphqlError(err)","after":"(err as GitHubAPIError).status !== 502"},{"id":"github-retry-caller","file":"src/github.ts","line":477,"before":"!isTransientGraphqlError(err)","after":"(err as GitHubAPIError).status !== 502"}],"timeout_seconds":30,"fail_exit_codes":[1]},"maintained_result":{"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","command":"python /tmp/hq372-2915-runtime-test-driver.py {test}","timeout_seconds":30,"recovered":null,"baseline":{"pure":{"result":"pass","exit_code":0,"seconds":2.631,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"result":"pass","exit_code":0,"seconds":2.56,"line":"{\"candidate\": \"api\", \"outcome\": \"passed\", \"tests_run\": 26, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"result":"pass","exit_code":0,"seconds":3.209,"line":"{\"candidate\": \"github\", \"outcome\": \"passed\", \"tests_run\": 20, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"matrix":[{"mutation":"empty-errors","file":"src/github-api.ts","line":192,"before":"graphqlErrors.length > 0","after":"true","results":{"pure":{"outcome":"caught","exit_code":1,"seconds":2.868,"line":"{\"candidate\": \"pure\", \"outcome\": \"assertion-failure\", \"tests_run\": 12, \"assertion_failures\": 1, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"survived","exit_code":0,"seconds":2.805,"line":"{\"candidate\": \"api\", \"outcome\": \"passed\", \"tests_run\": 26, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"survived","exit_code":0,"seconds":3.266,"line":"{\"candidate\": \"github\", \"outcome\": \"passed\", \"tests_run\": 20, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":["pure"]},{"mutation":"api-retry-caller","file":"src/github-api.ts","line":322,"before":"!isTransientGraphqlError(err)","after":"(err as GitHubAPIError).status !== 502","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":2.484,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"caught","exit_code":1,"seconds":2.709,"line":"{\"candidate\": \"api\", \"outcome\": \"assertion-failure\", \"tests_run\": 26, \"assertion_failures\": 7, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"survived","exit_code":0,"seconds":2.549,"line":"{\"candidate\": \"github\", \"outcome\": \"passed\", \"tests_run\": 20, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":["api"]},{"mutation":"github-retry-caller","file":"src/github.ts","line":477,"before":"!isTransientGraphqlError(err)","after":"(err as GitHubAPIError).status !== 502","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":2.563,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"survived","exit_code":0,"seconds":2.513,"line":"{\"candidate\": \"api\", \"outcome\": \"passed\", \"tests_run\": 26, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"caught","exit_code":1,"seconds":2.551,"line":"{\"candidate\": \"github\", \"outcome\": \"assertion-failure\", \"tests_run\": 20, \"assertion_failures\": 3, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":["github"]}],"tests":{"pure":{"catches":["empty-errors"],"complete":true,"subsumed_by":[],"only_it_catches":["empty-errors"]},"api":{"catches":["api-retry-caller"],"complete":true,"subsumed_by":[],"only_it_catches":["api-retry-caller"]},"github":{"catches":["github-retry-caller"],"complete":true,"subsumed_by":[],"only_it_catches":["github-retry-caller"]}},"uncaught":[],"coverage":"full","verification":{"files":[{"file":"src/github-api.ts","byte_identical":true},{"file":"src/github.ts","byte_identical":true}],"git_status_unchanged":true}},"guard":{"command":["python","/home/node/.codex/plugins/cache/hq/hq/0.20.0/skills/hq-create-test/scripts/kill_matrix.py","/tmp/hq372-2915-runtime-matrix-2-input.json"],"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","limits":{"deadline_seconds":45.0,"rss_cap_mib":512.0,"memory_floor_mib":2048.0,"sample_seconds":0.25,"grace_seconds":3},"started_at":"2026-10-25T05:18:14.547823+00:00","ended_at":"2026-10-25T05:18:47.699108+00:00","elapsed_seconds":33.1547057289863,"outcome":"passed","exit_code":0,"owned_survivor_count":0}}],"source_frames":{"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a":{"source_sha256":{"src/github-api.ts":"db78176ba580a54ee42f2fbacc719da517a97dc3f04c4b1f49a3b718080ab4b2","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"abc2b9d33681c8ec91db238a509b22d0daef75dae3e7263ea11ca67fc8b0a977","src/github.ts":"8c3f375dbc7d3bc0b414ea5dba136f5e2b70bf906ce5b5dd6204402ced16fa2a","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}},"bdbf34b2a273e1f4f8cf5519c0b7dfbcffa445c293f0a4912828fcbba31b7196":{"source_sha256":{"src/github-api.ts":"cef65106cd2549a94ded41c14a0bc40a410f8b203a1cc9dd0d9483f072e731b8","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"48b1e8743e1f4371b8593c36bc138ca8fe5139099b548764f7809f46095c10a6","src/github.ts":"8c3f375dbc7d3bc0b414ea5dba136f5e2b70bf906ce5b5dd6204402ced16fa2a","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}},"c9bcd821e11f35b4fb9df641f7b4177a8feb70474c73dc6f5ae37cedc00a9c12":{"source_sha256":{"src/github-api.ts":"042396493a0b6962751ac35abfd8b8ed4314c49ebb0c885c566b547a8fe513c0","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"965e1e1f104de1f0ffb7c97aff0bab14237f8a67bdb109a5623fb0dc63285932","src/github.ts":"8c3f375dbc7d3bc0b414ea5dba136f5e2b70bf906ce5b5dd6204402ced16fa2a","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}},"41afb2d98c4e3ee1e07881cec1b2d1bed6890b9ba3bcd0879c0f6c344a8baf1f":{"source_sha256":{"src/github-api.ts":"7e7612ecd3c9d4f93810b0f45e1d91d97b9b08b522b5d6d11812c74b1e9c5dec","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"a1b85c1846d6e3a6e8526157b810762db861c38b103f3c5beec660e58c6aa9e5","src/github.ts":"8c3f375dbc7d3bc0b414ea5dba136f5e2b70bf906ce5b5dd6204402ced16fa2a","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}},"fe3d8559e7d7fa999c3a9aabf7f3f1d5814b4efefee5e1496c3ef5ed772b6ba8":{"source_sha256":{"src/github-api.ts":"875f0f6f6190a5e441ea6758c3eeaca382dcdabf5bae51ec0b073712c9f40ecf","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"bead011d826136f0e25f676a02ccf92625930469ec0b0e6f972b9481dc3f54a1","src/github.ts":"8c3f375dbc7d3bc0b414ea5dba136f5e2b70bf906ce5b5dd6204402ced16fa2a","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}},"ef75e17f5f7157f191e68020a34df30c859481a171b757689c6da3ecb28665b7":{"source_sha256":{"src/github-api.ts":"a7f5d1a7805f6152f16dd86f6db2a3b9dfa6f6378f449de462dbd8fab72710eb","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"9f791ca9bc2737e6b4924fec15895bf956dd059c9004444ecb1c10e331697c62","src/github.ts":"8c3f375dbc7d3bc0b414ea5dba136f5e2b70bf906ce5b5dd6204402ced16fa2a","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}},"89d5a0adc3267e48fbd50afc7c55f771202b138c64771ca91cd1fe1de830171b":{"source_sha256":{"src/github-api.ts":"714ec3dd2225dd0afc923195eaf90cbc42ecda87aed6a10adfb09c5d5f0760b6","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"df94db8c6b18742dc5019ba10a8329fc0288686230bb4aa73f6f61c21f3bfe11","src/github.ts":"8c3f375dbc7d3bc0b414ea5dba136f5e2b70bf906ce5b5dd6204402ced16fa2a","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}},"6e7c69a2bd432c6831ce214e49ceef9428572fc87cb0ae875de397597336c8f6":{"source_sha256":{"src/github-api.ts":"00ba55d91d8e7f5cc776c94429b519608cbe0d6f82bbb0057b87a54dae096e33","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"be0ec4896e0607e681694300b15c8c51853cb63e137c36ef37f419cf3af3cfa8","src/github.ts":"8c3f375dbc7d3bc0b414ea5dba136f5e2b70bf906ce5b5dd6204402ced16fa2a","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}},"416d159f322e827a224fc678eb35a3182d77d980d32f7ec9384ecbccb3ec75ad":{"source_sha256":{"src/github-api.ts":"69d7c804d549119035d5ce443596ce80b772f03994706fe031ba57e3adf4fbb1","src/github.ts":"e8812d414c99f4f1058c457c6d0512c278a67512d5b4cd19597daa962c42a0e4","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"9c4646c0c476fa322550d66218be90eb16494797a8fa2a8340ae523fed9284d7","src/github.ts":"8c3f375dbc7d3bc0b414ea5dba136f5e2b70bf906ce5b5dd6204402ced16fa2a","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}},"f5999aefa12c8c22c1ead1abe869a0484c120e2a3231c2ed10bd7c1d2a992828":{"source_sha256":{"src/github-api.ts":"db78176ba580a54ee42f2fbacc719da517a97dc3f04c4b1f49a3b718080ab4b2","src/github.ts":"bb00d880017bccde10756505e2888effd8cf7141a9aaa7a42dc9aea32f795647","test/github.ts":"88ac5dcb435ae586bc3ae66010a9f57e976d5168931664ee0aef2725c67e5320"},"generated_js_sha256":{"src/github-api.ts":"abc2b9d33681c8ec91db238a509b22d0daef75dae3e7263ea11ca67fc8b0a977","src/github.ts":"fe8b017ba52ec95e3448050845b697e8fb4d0dec2560e0a336e9998b7628de5c","test/github.ts":"fb7b620da6828adddc117df3dd8ce795a7e05b7267b7e8fda5ea26467f7c91ae"}}},"framework_observations":[{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a"},{"candidate":"api","tests_run":26,"passes":26,"assertion_failures":0,"child_exit":0,"stdout_sha256":"1284d28375710b960316c9d9c25f416634d5794dda94b52b68eca0d3be300e89","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"passed","source_frame":"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a"},{"candidate":"github","tests_run":20,"passes":20,"assertion_failures":0,"child_exit":0,"stdout_sha256":"c487e6bacd2cc27bbf308bacfd7adf540ae5a7e04a513962335e540971a4af16","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"passed","source_frame":"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"bdbf34b2a273e1f4f8cf5519c0b7dfbcffa445c293f0a4912828fcbba31b7196"},{"candidate":"api","tests_run":26,"passes":19,"assertion_failures":7,"child_exit":7,"stdout_sha256":"1fe8a1106e95dfe17697e83e18502d6c18f2f234d817891544c35d3c6acf2067","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"assertion-failure","source_frame":"bdbf34b2a273e1f4f8cf5519c0b7dfbcffa445c293f0a4912828fcbba31b7196"},{"candidate":"github","tests_run":20,"passes":13,"assertion_failures":7,"child_exit":7,"stdout_sha256":"7697655397c242c5d98df9cb39c0c99e1ecdee986912763c3f993df3dcd30d1e","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"assertion-failure","source_frame":"bdbf34b2a273e1f4f8cf5519c0b7dfbcffa445c293f0a4912828fcbba31b7196"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"c9bcd821e11f35b4fb9df641f7b4177a8feb70474c73dc6f5ae37cedc00a9c12"},{"candidate":"api","tests_run":26,"passes":25,"assertion_failures":1,"child_exit":1,"stdout_sha256":"4885eca307a9b93a5ee6a85d9879708df7b2f55272649f77de08f4604885c419","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"assertion-failure","source_frame":"c9bcd821e11f35b4fb9df641f7b4177a8feb70474c73dc6f5ae37cedc00a9c12"},{"candidate":"github","tests_run":20,"passes":19,"assertion_failures":1,"child_exit":1,"stdout_sha256":"b38ad30f9810ae7f792755bae4c2b167db26b45ef89fd7ce26a39b07ae74bed1","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"assertion-failure","source_frame":"c9bcd821e11f35b4fb9df641f7b4177a8feb70474c73dc6f5ae37cedc00a9c12"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"41afb2d98c4e3ee1e07881cec1b2d1bed6890b9ba3bcd0879c0f6c344a8baf1f"},{"candidate":"api","tests_run":26,"passes":25,"assertion_failures":1,"child_exit":1,"stdout_sha256":"e60f76cde9a819f9e72fd36a17f1323996fa4e3d5065c25e72e5d1aa5fc0d559","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"assertion-failure","source_frame":"41afb2d98c4e3ee1e07881cec1b2d1bed6890b9ba3bcd0879c0f6c344a8baf1f"},{"candidate":"github","tests_run":20,"passes":19,"assertion_failures":1,"child_exit":1,"stdout_sha256":"62fb210bd3f02a30ac91f8e6163aa503e93aff49da7d0429cf4b432dee23e029","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"assertion-failure","source_frame":"41afb2d98c4e3ee1e07881cec1b2d1bed6890b9ba3bcd0879c0f6c344a8baf1f"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a"},{"candidate":"api","tests_run":26,"passes":26,"assertion_failures":0,"child_exit":0,"stdout_sha256":"1284d28375710b960316c9d9c25f416634d5794dda94b52b68eca0d3be300e89","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"passed","source_frame":"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a"},{"candidate":"github","tests_run":20,"passes":20,"assertion_failures":0,"child_exit":0,"stdout_sha256":"c487e6bacd2cc27bbf308bacfd7adf540ae5a7e04a513962335e540971a4af16","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"passed","source_frame":"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"fe3d8559e7d7fa999c3a9aabf7f3f1d5814b4efefee5e1496c3ef5ed772b6ba8"},{"candidate":"api","tests_run":26,"passes":25,"assertion_failures":1,"child_exit":1,"stdout_sha256":"d2518a9691a4a11af0f4d52a33e68e335e474417fe76de6a7a969b1d1defb912","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"assertion-failure","source_frame":"fe3d8559e7d7fa999c3a9aabf7f3f1d5814b4efefee5e1496c3ef5ed772b6ba8"},{"candidate":"github","tests_run":20,"passes":19,"assertion_failures":1,"child_exit":1,"stdout_sha256":"0b5b1bc436f220e5ff90877296859b87341ae5f465603152e2bfe00679ba795b","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"assertion-failure","source_frame":"fe3d8559e7d7fa999c3a9aabf7f3f1d5814b4efefee5e1496c3ef5ed772b6ba8"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"ef75e17f5f7157f191e68020a34df30c859481a171b757689c6da3ecb28665b7"},{"candidate":"api","tests_run":26,"passes":25,"assertion_failures":1,"child_exit":1,"stdout_sha256":"ef5a448de35944577eefbc50a2fd4cc37695ce6c48b00a94409533ae6bbbee28","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"assertion-failure","source_frame":"ef75e17f5f7157f191e68020a34df30c859481a171b757689c6da3ecb28665b7"},{"candidate":"github","tests_run":20,"passes":19,"assertion_failures":1,"child_exit":1,"stdout_sha256":"834937992b735055467758bd0ec25e709c2a2bd8723309b2af9d796ecd2d5ad9","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"assertion-failure","source_frame":"ef75e17f5f7157f191e68020a34df30c859481a171b757689c6da3ecb28665b7"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"89d5a0adc3267e48fbd50afc7c55f771202b138c64771ca91cd1fe1de830171b"},{"candidate":"api","tests_run":26,"passes":22,"assertion_failures":4,"child_exit":4,"stdout_sha256":"101ddbf2454a17a039d940bf296b4dee824bedeef9f6a578788f82759aebd2b6","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"assertion-failure","source_frame":"89d5a0adc3267e48fbd50afc7c55f771202b138c64771ca91cd1fe1de830171b"},{"candidate":"github","tests_run":20,"passes":16,"assertion_failures":4,"child_exit":4,"stdout_sha256":"07dd8a3aea6d8df30368169a94e20f6bd8a5c74ecd1200c29d405a4a1df90be0","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"assertion-failure","source_frame":"89d5a0adc3267e48fbd50afc7c55f771202b138c64771ca91cd1fe1de830171b"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a"},{"candidate":"api","tests_run":26,"passes":26,"assertion_failures":0,"child_exit":0,"stdout_sha256":"1284d28375710b960316c9d9c25f416634d5794dda94b52b68eca0d3be300e89","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"passed","source_frame":"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a"},{"candidate":"github","tests_run":20,"passes":20,"assertion_failures":0,"child_exit":0,"stdout_sha256":"c487e6bacd2cc27bbf308bacfd7adf540ae5a7e04a513962335e540971a4af16","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"passed","source_frame":"f8ca91019085ca8628c66430a724158e9a53d0e5fc7adb0cd1b8bfe6d060f64a"},{"candidate":"pure","tests_run":12,"passes":11,"assertion_failures":1,"child_exit":1,"stdout_sha256":"3e416b09626e721719167da73b981b47075080e0f8a26f4c7fbcb0e69c71748e","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"assertion-failure","source_frame":"6e7c69a2bd432c6831ce214e49ceef9428572fc87cb0ae875de397597336c8f6"},{"candidate":"api","tests_run":26,"passes":26,"assertion_failures":0,"child_exit":0,"stdout_sha256":"1284d28375710b960316c9d9c25f416634d5794dda94b52b68eca0d3be300e89","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"passed","source_frame":"6e7c69a2bd432c6831ce214e49ceef9428572fc87cb0ae875de397597336c8f6"},{"candidate":"github","tests_run":20,"passes":20,"assertion_failures":0,"child_exit":0,"stdout_sha256":"c487e6bacd2cc27bbf308bacfd7adf540ae5a7e04a513962335e540971a4af16","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"passed","source_frame":"6e7c69a2bd432c6831ce214e49ceef9428572fc87cb0ae875de397597336c8f6"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"416d159f322e827a224fc678eb35a3182d77d980d32f7ec9384ecbccb3ec75ad"},{"candidate":"api","tests_run":26,"passes":19,"assertion_failures":7,"child_exit":7,"stdout_sha256":"c7338d57b4af53e47a04759511d44b055dcc25d86a610d745020675b9b4b7271","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"assertion-failure","source_frame":"416d159f322e827a224fc678eb35a3182d77d980d32f7ec9384ecbccb3ec75ad"},{"candidate":"github","tests_run":20,"passes":20,"assertion_failures":0,"child_exit":0,"stdout_sha256":"c487e6bacd2cc27bbf308bacfd7adf540ae5a7e04a513962335e540971a4af16","stderr_sha256":"2c19db3c3e3fca233611453845e1bd73ea5b7b2112852e3d9ce5ed840766eac4","outcome":"passed","source_frame":"416d159f322e827a224fc678eb35a3182d77d980d32f7ec9384ecbccb3ec75ad"},{"candidate":"pure","tests_run":12,"passes":12,"assertion_failures":0,"child_exit":0,"stdout_sha256":"2d79c885f1804b5f9822caf3b22a22fba426f0a62782ddd1ebbc2ada26902726","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"passed","source_frame":"f5999aefa12c8c22c1ead1abe869a0484c120e2a3231c2ed10bd7c1d2a992828"},{"candidate":"api","tests_run":26,"passes":26,"assertion_failures":0,"child_exit":0,"stdout_sha256":"1284d28375710b960316c9d9c25f416634d5794dda94b52b68eca0d3be300e89","stderr_sha256":"07619afddaf78ce767cacc34ca00f9051211817162b4f40ea43fe21a53936d09","outcome":"passed","source_frame":"f5999aefa12c8c22c1ead1abe869a0484c120e2a3231c2ed10bd7c1d2a992828"},{"candidate":"github","tests_run":20,"passes":17,"assertion_failures":3,"child_exit":3,"stdout_sha256":"db283b912f5d465731bede7124c0d9cf421d057ed8d59585510cd5b90d584fe7","stderr_sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","outcome":"assertion-failure","source_frame":"f5999aefa12c8c22c1ead1abe869a0484c120e2a3231c2ed10bd7c1d2a992828"}],"reporter_calibration":{"expected_child_exit":4,"actual_child_exit":4,"report":{"tests":4,"passes":0,"pending":0,"failures":4,"assertion_failures":2,"runtime_errors":2},"controlled_production_mutations":false},"observer_fingerprints":{"transpile_batch.cjs":"5060babe7a9a0461884e5bf44478422896b779f61bf673dbda39c2ea9317a1ad","runtime_test_driver.py":"f3ae2e560ea3c03ae3c0743fa17c33a11ec74ae8bb23608a57afcf77a3319ecd","runtime_reporter.cjs":"1439bd25094cdea024149be7475022248eebc636c7b13398efd3c7e8618e79f3","reporter_controls.cjs":"e0760d079c87e9c48d728069baf952abf3363f32e9844fa43754059d523a71dd","calibrate_reporter.py":"f6253416b3134bc448fe22540f9e60da0134a666d4d20553a9e0b215d8548682","public_boundary_probe.cjs":"78bd2df8c4c1dec3d653485e64184aade20d04f6f7c68160a9422fb9d5fe0aea"},"maintained_runner":{"sha256":"b57f61c8bcaffe5c74615543dfe1d832022a62910efb422f80ccaa0acd346ed2","bytes":32104},"maintained_runner_terminal":"exit-status-only; local constructor observations are separate qualified evidence, not a maintained Mocha/native adapter","framework_observation_common":{"kind":"local transpile-only runtime mutation observation","loaded_module_attestation":null,"native_harness":null,"observer_version":2,"runtime_errors":0,"source_unchanged_during_run":true,"typed_build":false},"common_fields_are_lossless_factoring":true},"development_history":{"first_invalid_matrix":{"cwd":"/workspace/.worktrees/hq372-upstream2915-correction","command":"python /tmp/hq372-2915-runtime-test-driver.py {test}","timeout_seconds":30,"recovered":null,"baseline":{"pure":{"result":"pass","exit_code":0,"seconds":3.149,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"result":"pass","exit_code":0,"seconds":2.896,"line":"{\"candidate\": \"api\", \"outcome\": \"passed\", \"tests_run\": 26, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"result":"pass","exit_code":0,"seconds":2.638,"line":"{\"candidate\": \"github\", \"outcome\": \"passed\", \"tests_run\": 20, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}}},"matrix":[{"mutation":"all-errors","file":"src/github-api.ts","line":193,"before":"graphqlErrors.every","after":"graphqlErrors.some","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":2.586,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"error","exit_code":2,"seconds":2.875,"line":"exit code 2 is not a test failure; last output: {\"candidate\": \"api\", \"outcome\": \"runtime-or-framework-error\", \"tests_run\": 26, \"assertion_failures\": 0, \"runtime_errors\": 7}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"error","exit_code":2,"seconds":3.182,"line":"exit code 2 is not a test failure; last output: {\"candidate\": \"github\", \"outcome\": \"runtime-or-framework-error\", \"tests_run\": 20, \"assertion_failures\": 0, \"runtime_errors\": 7}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":[]},{"mutation":"message-prefix","file":"src/github-api.ts","line":200,"before":"startsWith(text)","after":"includes(text)","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":2.837,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"error","exit_code":2,"seconds":2.755,"line":"exit code 2 is not a test failure; last output: {\"candidate\": \"api\", \"outcome\": \"runtime-or-framework-error\", \"tests_run\": 26, \"assertion_failures\": 0, \"runtime_errors\": 1}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"error","exit_code":2,"seconds":2.774,"line":"exit code 2 is not a test failure; last output: {\"candidate\": \"github\", \"outcome\": \"runtime-or-framework-error\", \"tests_run\": 20, \"assertion_failures\": 0, \"runtime_errors\": 1}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":[]},{"mutation":"error-type","file":"src/github-api.ts","line":197,"before":"(type === undefined || type === 'INTERNAL')","after":"true","results":{"pure":{"outcome":"survived","exit_code":0,"seconds":2.605,"line":"{\"candidate\": \"pure\", \"outcome\": \"passed\", \"tests_run\": 12, \"assertion_failures\": 0, \"runtime_errors\": 0}","terminal":{"framework":null,"kind":"exit-status-only"}},"api":{"outcome":"error","exit_code":2,"seconds":2.504,"line":"exit code 2 is not a test failure; last output: {\"candidate\": \"api\", \"outcome\": \"runtime-or-framework-error\", \"tests_run\": 26, \"assertion_failures\": 0, \"runtime_errors\": 1}","terminal":{"framework":null,"kind":"exit-status-only"}},"github":{"outcome":"error","exit_code":2,"seconds":2.516,"line":"exit code 2 is not a test failure; last output: {\"candidate\": \"github\", \"outcome\": \"runtime-or-framework-error\", \"tests_run\": 20, \"assertion_failures\": 0, \"runtime_errors\": 1}","terminal":{"framework":null,"kind":"exit-status-only"}}},"caught_by":[]}],"tests":{"pure":{"catches":[],"complete":true,"subsumed_by":["api","github"],"only_it_catches":[]},"api":{"catches":[],"complete":false,"subsumed_by":null,"only_it_catches":[]},"github":{"catches":[],"complete":false,"subsumed_by":null,"only_it_catches":[]}},"uncaught":["all-errors","message-prefix","error-type"],"coverage":"none","verification":{"files":[{"file":"src/github-api.ts","byte_identical":true}],"git_status_unchanged":true}},"first_invalid_driver":{"sha256":"c2395c51cfa24599bdb12ce9db977927d08b10cd6da2ad114e17f216f5c25de3","bytes":3284},"failures_retained":["All three unchanged-bound typed builds stopped at RSS cap; not type errors or passes.","The first test call had no compiled file after the interrupted build; no test result.","Initial first matrix misclassified errors because Mocha JSON omits inherited Error.name and CLI returns failure counts. It supplies no coverage credit.","Calibrated reporter observes actual Node/Chai assertion constructors and distinguishes two runtime controls including a spoofed name; invalid first matrix remains invalid.","Initial formatting check refused test/github.ts; maintained formatter corrected it.","The compiled mutation derivatives were regenerated from restored source before the final69-case replay."],"Aperture":"TypeScript diagnostics unsupported; caller lookup returned another worktree, so exact current reference/coverage binding unavailable. Explicit current source reads/search and actual tests used."},"tools_source":{"transpile_batch.cjs":"const fs=require('node:fs'),path=require('node:path');\nconst root=process.argv[2],ts=require(path.join(root,'node_modules/typescript'));\nconst config=ts.getParsedCommandLineOfConfigFile(path.join(root,'tsconfig.json'),{}, {...ts.sys,onUnRecoverableConfigFileDiagnostic:e=>{throw Error(ts.flattenDiagnosticMessageText(e.messageText,'\\n'));}});\nif(config.errors.length)throw Error('source configuration refused');\nfor(const name of process.argv.slice(3)){\n const source=path.join(root,name);\n const result=ts.transpileModule(fs.readFileSync(source,'utf8'),{fileName:source,compilerOptions:config.options,reportDiagnostics:true});\n const errors=(result.diagnostics||[]).filter(x=>x.category===ts.DiagnosticCategory.Error);\n if(errors.length)throw Error(ts.formatDiagnosticsWithColorAndContext(errors,{getCurrentDirectory:()=>root,getCanonicalFileName:x=>x,getNewLine:()=>'\\n'}));\n const destination=path.join(root,'build',name.replace(/\\.ts$/,'.js'));\n fs.mkdirSync(path.dirname(destination),{recursive:true});\n fs.writeFileSync(destination,result.outputText);\n if(result.sourceMapText)fs.writeFileSync(destination+'.map',result.sourceMapText);\n}\n","runtime_test_driver.py":"import hashlib,json,os,subprocess,sys\nfrom pathlib import Path\nroot=Path('/workspace/.worktrees/hq372-upstream2915-correction')\ngroups={\n    'pure':('GitHub isTransientGraphqlError',12),\n    'api':('GitHub graphqlRequest backoff GitHubApi preserves read retry policy',26),\n    'github':('GitHub graphqlRequest backoff GitHub preserves read retry policy',20),\n}\npattern,expected=groups[sys.argv[1]]\nfiles=['src/github-api.ts','src/github.ts','test/github.ts']\nhashes=lambda:{name:hashlib.sha256((root/name).read_bytes()).hexdigest() for name in files}\nbefore=hashes()\nrecord={'observer_version':2,'observer_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),'reporter_sha256':hashlib.sha256(Path('/tmp/hq372-2915-runtime-reporter.cjs').read_bytes()).hexdigest(),'kind':'local transpile-only runtime mutation observation','candidate':sys.argv[1],'source_sha256':before,'native_harness':None,'loaded_module_attestation':None,'typed_build':False}\ntry:\n    compile_run=subprocess.run(['node','/tmp/hq372-2915-transpile-batch.cjs',str(root),*files],cwd=root,capture_output=True,timeout=20)\n    if compile_run.returncode:\n        record.update(outcome='transpile-error',compile_exit=compile_run.returncode)\n        code=2\n    else:\n        record['generated_js_sha256']={name:hashlib.sha256((root/'build'/Path(name).with_suffix('.js')).read_bytes()).hexdigest() for name in files}\n        process=subprocess.run([str(root/'node_modules/.bin/mocha'),'--timeout','5000','build/test/github.js','--grep',pattern,'--reporter','/tmp/hq372-2915-runtime-reporter.cjs'],cwd=root,env={**os.environ,'ENVIRONMENT':'test','LC_ALL':'en','SNAPSHOT_SKIP_PRUNING':'1'},capture_output=True,timeout=20)\n        stdout=process.stdout.decode(errors='replace')\n        lines=[line[len('HQ_RUNTIME_REPORT '):] for line in stdout.splitlines() if line.startswith('HQ_RUNTIME_REPORT ')]\n        if len(lines)!=1:\n            raise ValueError('report-cardinality')\n        report=json.loads(lines[0]);failures=report['failures']\n        if report['tests']!=expected or report['pending']!=0:\n            raise ValueError('selection-or-framework-mismatch')\n        assertions=report['assertion_failures'];errors=report['runtime_errors']\n        if failures!=assertions+errors or report['tests']!=report['passes']+failures:\n            raise ValueError('framework-count-mismatch')\n        record.update(tests_run=report['tests'],passes=report['passes'],assertion_failures=assertions,runtime_errors=errors,child_exit=process.returncode,stdout_sha256=hashlib.sha256(process.stdout).hexdigest(),stderr_sha256=hashlib.sha256(process.stderr).hexdigest())\n        if process.returncode==0 and not failures:\n            code=0;record['outcome']='passed'\n        elif process.returncode==min(failures,255) and failures and not errors:\n            code=1;record['outcome']='assertion-failure'\n        else:\n            code=2;record['outcome']='runtime-or-framework-error'\nexcept (subprocess.TimeoutExpired,ValueError,KeyError) as error:\n    code=2;record['outcome']='observation-error';record['error_class']=type(error).__name__\nafter=hashes()\nrecord['source_unchanged_during_run']=before==after\nif before!=after:\n    code=2;record['outcome']='concurrent-source-change'\nwith Path('/tmp/hq372-2915-runtime-mutation-observations-v2.jsonl').open('a') as stream:\n    stream.write(json.dumps(record,separators=(',',':'))+'\\n')\nprint(json.dumps({k:record[k] for k in ['candidate','outcome','tests_run','assertion_failures','runtime_errors'] if k in record}))\nraise SystemExit(code)\n","runtime_reporter.cjs":"const root='/workspace/.worktrees/hq372-upstream2915-correction';\nconst Mocha=require(root+'/node_modules/mocha');\nconst {createRequire}=require('node:module');\nconst chaiRequire=createRequire(root+'/node_modules/chai/package.json');\nconst ChaiAssertionError=chaiRequire('assertion-error');\nconst NodeAssertionError=require('node:assert').AssertionError;\nconst {EVENT_TEST_FAIL,EVENT_RUN_END}=Mocha.Runner.constants;\nmodule.exports=class RuntimeReporter extends Mocha.reporters.Base {\n constructor(runner,options){\n  super(runner,options);\n  let assertions=0,runtime=0;\n  runner.on(EVENT_TEST_FAIL,(_test,error)=>{\n   if(error instanceof ChaiAssertionError || error instanceof NodeAssertionError)assertions++;\n   else runtime++;\n  });\n  runner.once(EVENT_RUN_END,()=>{\n   const s=runner.stats;\n   process.stdout.write('HQ_RUNTIME_REPORT '+JSON.stringify({tests:s.tests,passes:s.passes,pending:s.pending,failures:s.failures,assertion_failures:assertions,runtime_errors:runtime})+'\\n');\n  });\n }\n};\n","reporter_controls.cjs":"const assert=require('node:assert/strict');\nconst {expect}=require('/workspace/.worktrees/hq372-upstream2915-correction/node_modules/chai');\ndescribe('reporter real error constructors',()=>{\n it('Node assertion',()=>assert.equal(1,2));\n it('Chai assertion',()=>expect(false).to.equal(true));\n it('runtime TypeError',()=>{throw new TypeError('intentional runtime control');});\n it('spoofed name',()=>{const e=new Error('intentional name control');e.name='AssertionError';throw e;});\n});\n","calibrate_reporter.py":"import json,subprocess\nfrom pathlib import Path\nroot=Path('/workspace/.worktrees/hq372-upstream2915-correction')\np=subprocess.run([str(root/'node_modules/.bin/mocha'),'--timeout','5000','--reporter','/tmp/hq372-2915-runtime-reporter.cjs','/tmp/hq372-2915-reporter-controls.cjs'],cwd=root,capture_output=True,text=True,timeout=20)\nlines=[line[len('HQ_RUNTIME_REPORT '):] for line in p.stdout.splitlines() if line.startswith('HQ_RUNTIME_REPORT ')]\nassert len(lines)==1\nreport=json.loads(lines[0])\nassert p.returncode==4,(p.returncode,p.stderr)\nassert report=={'tests':4,'passes':0,'pending':0,'failures':4,'assertion_failures':2,'runtime_errors':2},report\nPath('/tmp/hq372-2915-reporter-calibration.json').write_text(json.dumps({'expected_child_exit':4,'actual_child_exit':p.returncode,'report':report,'controlled_production_mutations':False},indent=2)+'\\n')\nprint(json.dumps({'reporter_calibrated':True,'real_assertions':2,'runtime_errors_including_spoofed_name':2,'child_exit_is_failure_count':True}))\n","public_boundary_probe.cjs":"const assert=require('node:assert/strict');\nconst fs=require('node:fs');\nconst root='/workspace/.worktrees/hq372-upstream2915-correction';\nconst {GitHub}=require(root+'/build/src/github.js');\nconst {GitHubApi}=require(root+'/build/src/github-api.js');\nconst fixture=JSON.parse(fs.readFileSync(root+'/test/fixtures/commits-since-no-files.json','utf8'));\nconst errors={mixed:[{message:'Something went wrong while executing your query.'},{message:'Resource not accessible by integration'}],quoted:[{message:\"Field 'Something went wrong while executing your query' doesn't exist on type 'Query'\"}]};\n(async()=>{\n const observed=[];\n for(const client of ['GitHub','GitHubApi'])for(const [kind,failures]of Object.entries(errors)){\n  let requests=0;let refused=false;\n  const options={owner:'fake',repo:'fake',defaultBranch:'main',fetch:async()=>{\n   requests++;\n   const repository={...fixture.repository,releases:{nodes:[{name:'v1.0.0',tag:{name:'v1.0.0'},tagCommit:{oid:'release-sha'},description:'notes',url:'https://example.com/release',isDraft:false}],pageInfo:{hasNextPage:false,endCursor:null}}};\n   return new Response(JSON.stringify(requests===1?{errors:failures}:{data:{repository}}),{status:200,headers:{'content-type':'application/json'}});\n  }};\n  try{\n   const iterator=client==='GitHub'?(await GitHub.create(options)).mergeCommitIterator('main',{batchSize:100}):(await GitHubApi.create(options)).releaseIterator();\n   await iterator.next();\n  }catch(error){refused=true;}\n  observed.push({client,kind,requests,refused});\n }\n console.log(JSON.stringify({real_public_clients:true,observed}));\n assert.deepEqual(observed.map(x=>({requests:x.requests,refused:x.refused})),Array(4).fill({requests:1,refused:true}),'permanent/mixed/quoted errors must be refused before retry');\n})().catch(e=>{console.error(e);process.exitCode=1;});\n"},"primary_sources_read_2026_10_25":["https://legacy.mochajs.org/api/tutorial-custom-reporter.html","https://mochajs.org/explainers/programmatic-usage/","actual locked mocha/lib/reporters/json.js errorJSON/Runner events and real-error calibration"],"qualification":["No source push/fork/new PR, author CI, rerun/cancel, approval, merge, publication, pin or settings change.","Typed checking and normal upstream full build/CI remain unverified; all interrupted commands retain failed outcomes.","Runtime uses locked TypeScript.transpileModule artifacts, not the normal typed build.","All generic maintained matrix outcomes retain exit-status-only qualification. Local reporter constructor counts are author-local observations, not native evidence.","public-definition-unavailable in the maintained receipt interface","exact-public-command-context-unavailable in the maintained receipt interface","native-harness-step-log-unavailable","wrapper-eventual-exit-unavailable in a maintained receipt; outer tool/guard exits separately observed","base-or-image-unavailable as native runtime authority","loaded-test-module-binding-unavailable","undeclared-dependencies-unavailable","The source/tests were executed as uncommitted bytes on3fe; final local21fa commit independently matches those exact bytes, without relabeling the execution as a committed/native run."]}

Assignee: CodeByPeace retains the source correction; maintainers retain design/admission, full type-check/CI, review, merge and publication. This appendix grants no approval or native acceptance.

@TBaltensperger

Copy link
Copy Markdown

Identity: TBaltensperger @ nuitio/hq

Applicable correction for this draft's exact head; runtime verified, type checking still owed.

@CodeByPeace, I prepared the two-file correction on your unchanged3fe617edc75bcc12daa727fcf76faec2cbe95189 so it can be applied directly here. It requires every GraphQL error to be recognized as transient, anchors recognized messages, and preserves permanent status/type and rate-limit refusal. Both existing public retry loops, backoff/page-size/exhaustion behavior and your dependency lock remain.

A real throwaway run through GitHub.mergeCommitIterator and GitHubApi.releaseIterator reproduces four wrong acceptances on3fe: both clients retry mixed/quoted permanent errors and accept the next response. The corrected source refuses all four on request1. The targeted runtime selection then passes 69tests/zero failures/pending: all18existing owning tests and five history/write-refusal controls stay, with46new public-client cases. Six already-covered GitHub scenarios remain in their existing tests instead of being duplicated in the new matrix. No tests are retired.

Type checking remains unverified: the normal full build, focused npm build and focused compiler-only invocation all hit the unchanged local512MiB guard before completion. These are interrupted checks, not type errors or passes. Runtime artifacts were generated with the locked TypeScript4.9.4 transpileModule API; their tests do not replace the normal typed build or upstream CI. The draft's package/lock differs from maintainedc589, so the prior typed58-test patch evidence retains its separate source frame. ESLint, Prettier and whitespace pass on these exact new files. No full npm test or upstream CI was run.

Complete safe source/runtime/mutation evidence records all failed attempts, exact tool sources and restored bindings. Nine semantic mutants are caught across27candidate cells. The calibrated framework reporter observes41assertion failures/zero runtime errors; the maintained matrix remains qualified exit-status-only, with native/loaded-module/dependency authority unproved. The first invalid observer result remains invalid. All mutant-generated outputs were rebuilt from restored sources before the final69-test replay. Aperture does not provide TypeScript diagnostics here.

The full patch is 11,211bytes, SHA256 0967c238021514569b40f1e47a9ff8d64d6d4d41a975a19d8d018c167599cd67. Applying it to an alternate index initialized at exact3fe reproduces tree0756aa5e5c5f628dd3f671673d019098d9f2ce1c while leaving the real index/worktree/HEAD unchanged. Local proposal commit21fa636c6f819f3c732fe6907d19fb8e8abcaf6c has sole parent3fe and matches the tested source bytes; it has not been pushed. This supplements the earlier canonical-main proposal with a correction bound to this actual draft. Your existing maintainer design/admission question remains with maintainers.

Complete directly applicable two-file patch
diff --git a/src/github-api.ts b/src/github-api.ts
index 8db3b5e..2a28170 100644
--- a/src/github-api.ts
+++ b/src/github-api.ts
@@ -154,23 +154,56 @@ export const MAX_SLEEP_SECONDS = 20;
 const TRANSIENT_GRAPHQL_STATUSES = [500, 502, 503, 504];
 const TRANSIENT_GRAPHQL_MESSAGES = [
   'something went wrong while executing your query',
-  'respond to your request in time',
+  "we couldn't respond to your request in time",
 ];
 
-export function isTransientGraphqlError(err: unknown): boolean {
-  const {status, errors} = (err ?? {}) as {
+/** Classify failures of the read-only GraphQL requests below. */
+export function isTransientGraphqlError(error: unknown): boolean {
+  if (!error || typeof error !== 'object') return false;
+  const {status, errors, headers, response} = error as {
     status?: number;
-    errors?: {message?: string}[];
+    errors?: unknown;
+    headers?: Record<string, string>;
+    response?: {
+      headers?: Record<string, string>;
+      data?: {errors?: unknown};
+    };
   };
-  if (typeof status === 'number') {
-    return TRANSIENT_GRAPHQL_STATUSES.includes(status);
+  const responseHeaders = headers ?? response?.headers;
+  // Leave rate-limit handling to the caller/transport, including HTTP 200 errors.
+  if (
+    responseHeaders?.['retry-after'] !== undefined ||
+    responseHeaders?.['x-ratelimit-remaining'] === '0'
+  ) {
+    return false;
   }
-  return (
-    Array.isArray(errors) &&
-    errors.some(e =>
-      TRANSIENT_GRAPHQL_MESSAGES.some(m => e.message?.toLowerCase().includes(m))
-    )
-  );
+  if (
+    status !== undefined &&
+    status !== 200 &&
+    !TRANSIENT_GRAPHQL_STATUSES.includes(status)
+  ) {
+    return false;
+  }
+  const graphqlErrors = errors ?? response?.data?.errors;
+  if (graphqlErrors !== undefined) {
+    // One transient field must not hide a permission, rate-limit or query error.
+    return (
+      Array.isArray(graphqlErrors) &&
+      graphqlErrors.length > 0 &&
+      graphqlErrors.every(entry => {
+        if (!entry || typeof entry !== 'object') return false;
+        const {message, type} = entry as {message?: unknown; type?: unknown};
+        return (
+          (type === undefined || type === 'INTERNAL') &&
+          typeof message === 'string' &&
+          TRANSIENT_GRAPHQL_MESSAGES.some(text =>
+            message.toLowerCase().startsWith(text)
+          )
+        );
+      })
+    );
+  }
+  return status !== undefined && TRANSIENT_GRAPHQL_STATUSES.includes(status);
 }
 
 export const MAX_ISSUE_BODY_SIZE = 65536;
diff --git a/test/github.ts b/test/github.ts
index dc6020c..92601e8 100644
--- a/test/github.ts
+++ b/test/github.ts
@@ -1221,6 +1221,105 @@ describe('GitHub', () => {
   });
 
   describe('graphqlRequest backoff', () => {
+    const internal = {
+      type: 'INTERNAL',
+      message: 'Something went wrong while executing your query.',
+    };
+    const timeout = {message: "We couldn't respond to your request in time."};
+    const forbidden = {
+      type: 'FORBIDDEN',
+      message: 'Resource not accessible by integration',
+    };
+    const rateLimit = {
+      type: 'RATE_LIMITED',
+      message: 'API rate limit exceeded',
+    };
+    const validation = {
+      message: "Field 'unknown' doesn't exist on type 'Query'",
+    };
+    const cases: {
+      name: string;
+      status: number;
+      errors?: object[];
+      headers?: Record<string, string>;
+      retry: boolean;
+      exhaust?: boolean;
+    }[] = [
+      ...[500, 502, 503, 504].map(status => ({
+        name: `HTTP ${status}`,
+        status,
+        retry: true,
+      })),
+      {name: 'internal error', status: 200, errors: [internal], retry: true},
+      {name: 'timeout error', status: 200, errors: [timeout], retry: true},
+      {
+        name: 'multiple transient errors',
+        status: 200,
+        errors: [internal, timeout],
+        retry: true,
+      },
+      ...[401, 403, 422, 429].map(status => ({
+        name: `non-transient HTTP ${status}`,
+        status,
+        errors: [internal],
+        retry: false,
+      })),
+      ...[forbidden, rateLimit, validation].flatMap(error => [
+        {name: error.message, status: 200, errors: [error], retry: false},
+        {
+          name: `mixed ${error.message}`,
+          status: 200,
+          errors: [internal, error],
+          retry: false,
+        },
+        {
+          name: `reversed mixed ${error.message}`,
+          status: 200,
+          errors: [error, internal],
+          retry: false,
+        },
+      ]),
+      {
+        name: 'server error with forbidden field',
+        status: 503,
+        errors: [internal, forbidden],
+        retry: false,
+      },
+      {
+        name: 'validation quoting transient wording',
+        status: 200,
+        errors: [
+          {
+            message:
+              "Field 'Something went wrong while executing your query' doesn't exist on type 'Query'",
+          },
+        ],
+        retry: false,
+      },
+      {
+        name: 'rate-limit type with transient-looking message',
+        status: 200,
+        errors: [{...internal, type: 'RATE_LIMITED'}],
+        retry: false,
+      },
+      ...[
+        {'retry-after': '60'},
+        {'x-ratelimit-remaining': '0', 'x-ratelimit-reset': '3600'},
+      ].map(headers => ({
+        name: `rate-limit headers ${Object.keys(headers)[0]}`,
+        status: 200,
+        errors: [internal],
+        headers,
+        retry: false,
+      })),
+      {
+        name: 'exhausted internal errors',
+        status: 200,
+        errors: [internal],
+        retry: true,
+        exhaust: true,
+      },
+    ];
     let clock: sinon.SinonFakeTimers;
     let realSetInterval: typeof setInterval;
     let realClearInterval: typeof clearInterval;
@@ -1234,6 +1333,132 @@ describe('GitHub', () => {
       clock.restore();
     });
 
+    for (const client of ['GitHub', 'GitHubApi'] as const) {
+      // The other existing GitHub tests already exercise these cases.
+      const existingGitHubCases = new Set([
+        'HTTP 500',
+        'HTTP 502',
+        'HTTP 503',
+        'HTTP 504',
+        'internal error',
+        validation.message,
+      ]);
+      const selected = cases.filter(
+        scenario =>
+          client === 'GitHubApi' || !existingGitHubCases.has(scenario.name)
+      );
+      for (const scenario of selected) {
+        it(`${client} preserves read retry policy for ${scenario.name}`, async () => {
+          const requests: {query: string; variables: {num: number}}[] = [];
+          const times: number[] = [];
+          const options = {
+            owner: 'fake',
+            repo: 'fake',
+            defaultBranch: 'main',
+            fetch: async (_url: string, init: {body: string}) => {
+              requests.push(JSON.parse(init.body));
+              times.push(clock.now);
+              const failed = requests.length === 1 || scenario.exhaust;
+              const repository = {
+                ...JSON.parse(
+                  readFileSync(
+                    resolve(fixturesPath, 'commits-since-no-files.json'),
+                    'utf8'
+                  )
+                ).repository,
+                releases: {
+                  nodes: [
+                    {
+                      name: 'v1.0.0',
+                      tag: {name: 'v1.0.0'},
+                      tagCommit: {oid: 'release-sha'},
+                      description: 'release notes',
+                      url: 'https://example.com/release',
+                      isDraft: false,
+                    },
+                  ],
+                  pageInfo: {hasNextPage: false, endCursor: null},
+                },
+              };
+              return new Response(
+                JSON.stringify(
+                  failed
+                    ? {errors: scenario.errors, message: 'Service unavailable'}
+                    : {data: {repository}}
+                ),
+                {
+                  status: failed ? scenario.status : 200,
+                  headers: {
+                    'content-type': 'application/json',
+                    ...(failed ? scenario.headers : {}),
+                  },
+                }
+              );
+            },
+          };
+          const iterator =
+            client === 'GitHub'
+              ? (await GitHub.create(options)).mergeCommitIterator('main', {
+                  batchSize: 100,
+                })
+              : (await GitHubApi.create(options)).releaseIterator();
+          let value: {sha: string} | undefined;
+          let failure: Error | undefined;
+          const pending = iterator.next().then(
+            result => {
+              if (!result.done) value = result.value;
+            },
+            error => {
+              failure = error;
+            }
+          );
+          await clock.runAllAsync();
+          await pending;
+
+          if (scenario.retry && !scenario.exhaust) {
+            expect(failure).to.be.undefined;
+            expect(value?.sha).to.equal(
+              client === 'GitHub'
+                ? 'e6daec403626c9987c7af0d97b34f324cd84320a'
+                : 'release-sha'
+            );
+          } else {
+            expect(value).to.be.undefined;
+            expect(failure?.name).to.equal(
+              scenario.status === 200
+                ? 'GraphqlResponseError'
+                : 'GitHubAPIError'
+            );
+          }
+          expect(requests).to.have.lengthOf(
+            scenario.exhaust ? 6 : scenario.retry ? 2 : 1
+          );
+          expect(times).to.deep.equal(
+            scenario.exhaust
+              ? [0, 1000, 3000, 7000, 15000, 31000]
+              : scenario.retry
+              ? [0, 1000]
+              : [0]
+          );
+          const original = requests[0];
+          for (const request of requests) {
+            expect(request.query.trim()).to.match(/^query\b/);
+            expect({...request.variables, num: original.variables.num}).to.eql(
+              original.variables
+            );
+            expect(request.query).to.equal(original.query);
+          }
+          if (scenario.exhaust) {
+            expect(requests.map(request => request.variables.num)).to.eql(
+              client === 'GitHub'
+                ? [100, 50, 25, 12, 6, 1]
+                : [25, 12, 6, 3, 1, 1]
+            );
+          }
+        });
+      }
+    }
+
     it('should halve the pagination size on 502 error retries, and force to 1 on the last attempt', async () => {
       let attempt = 0;
       req = nock('https://api.github.com')
@@ -1262,10 +1487,13 @@ describe('GitHub', () => {
         clock.tick(10000);
       }, 10);
 
-      await assert.rejects(promise, (error: Error) => {
-        return error instanceof GitHubAPIError && error.status === 502;
-      });
-      realClearInterval(tickInterval);
+      try {
+        await assert.rejects(promise, (error: Error) => {
+          return error instanceof GitHubAPIError && error.status === 502;
+        });
+      } finally {
+        realClearInterval(tickInterval);
+      }
       req.done();
     });
 

Requested through HQ372's downstream dependency repair and written by Codex as an HQ source contribution. No fork, new PR, source push, CI dispatch/retry/cancel, approval, merge, publication, pin or settings change.

Assignee: @CodeByPeace: incorporate the correction into this draft under its maintainer design/admission answer; release-please maintainers: own full type-check/CI, independent review, merge and publication. The HQ downstream contributor retains returned patch findings and eventual published dependency/locked-adoption follow-through.

The shared check retried if any error in the response matched a known
transient message, using a substring match. A response that also carried
a permanent error, or a permanent error that quoted a transient phrase,
was retried. An error entry that was null threw a TypeError inside the
retry handler.

Require every error to start with a known transient message, ignore
non-object entries, and treat an empty errors array as not transient.
The HTTP status branch is unchanged.

Refs googleapis#2905
@product-auto-label product-auto-label Bot added size: l Pull request size is large. and removed size: m Pull request size is medium. labels Oct 26, 2026
@CodeByPeace
CodeByPeace marked this pull request as ready for review October 26, 2026 18:27
@CodeByPeace
CodeByPeace requested review from a team as code owners October 26, 2026 18:27
@TBaltensperger

Copy link
Copy Markdown

Identity: TBaltensperger @ nuitio/hq

Checked the revised 4e1c71d: the mixed-error and quoted-validation controls now pass through both public client loops. Thank you for incorporating those corrections. The remaining concern is the status-first return and the unexamined rate-limit/type fields in the shared predicate.

A concrete current-head example returns true:

isTransientGraphqlError({
  status: 503,
  response: {headers: {'retry-after': '60'}}
});

That sends the error into the ordinary backoff even though the response supplies a delay. This combination need not be inferred from an outage: HTTP semantics explicitly permits Retry-After on 503, and GitHub's guidance says to honor Retry-After and exhausted-limit headers. Not claiming GitHub emitted these exact combinations in the reported incident.

The previously posted test matrix, run selectively against exact4e1 production transpilation, gives:

Controls, each through GitHub and GitHubApi Current result
Mixed permission errors; validation quoting transient text 4 pass
503 with a permanent field; RATE_LIMITED type with transient-looking text 4 assertion failures
Retry-After; exhausted-limit headers 4 assertion failures

The corrected proposal passes all12 selected cases. These are controlled local counterexamples, not the upstream suite or live API observations. Both preparatory harness attempts failed on missing copied package metadata before any selected test ran; the completed frame materializes that dependency and the exact declared test/production sources. It finished in17.55s under45s/512MiB/2048MiB-floor, with zero recorded survivors and truncated telemetry. Full TypeScript checking and upstream CI remain unverified; no CI was started or retried.

Please retain recognized permanent/type/header refusal before the broad transient classification, leaving server-directed/rate-limit handling to its appropriate path, and keep the cases in both loops. If maintainers choose a narrower scope, please state that boundary explicitly; the remaining controls should not be reported as already preserved. Maintainers retain design, CI, review and publication decisions. This feedback is not an approval or merge-readiness verdict.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: l Pull request size is large.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Retry transient read-only GraphQL failures beyond HTTP 502

2 participants