chore: improve agent harness guidance (#2185)

This commit is contained in:
Carlos Florêncio
2026-08-11 08:28:25 +01:00
committed by GitHub
parent a69a026741
commit 58e7be4b78
48 changed files with 1410 additions and 317 deletions
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env python3
"""Audit Playwright blob reports for retries, errors, and unexpected results."""
from __future__ import annotations
import argparse
import io
import json
import sys
import zipfile
from collections import Counter
from pathlib import Path, PurePosixPath
from typing import Iterable
EXPECTED_STATUSES = {"passed", "skipped"}
def is_report_jsonl(path: str | Path) -> bool:
name = Path(path).name
return name == "report.jsonl" or (name.startswith("report-") and name.endswith(".jsonl"))
def is_report_zip(path: str | Path) -> bool:
name = Path(path).name
return name.startswith("report-") and name.endswith(".zip")
def discover_reports(inputs: Iterable[Path]) -> list[tuple[str, Path, str | None]]:
"""Return sorted (kind, path, member) tuples without duplicate inputs."""
reports: dict[tuple[str, str, str | None], tuple[str, Path, str | None]] = {}
for input_path in inputs:
if not input_path.exists():
raise FileNotFoundError(input_path)
candidates = [input_path] if input_path.is_file() else sorted(input_path.rglob("*"))
for candidate in candidates:
if not candidate.is_file():
continue
if candidate.suffix == ".zip" and (is_report_zip(candidate) or candidate == input_path):
try:
with zipfile.ZipFile(candidate) as archive:
for member in archive.namelist():
if is_report_jsonl(PurePosixPath(member).name):
key = ("zip", str(candidate), member)
reports[key] = ("zip", candidate, member)
except zipfile.BadZipFile as exc:
raise ValueError(f"invalid Playwright report archive: {candidate}") from exc
elif is_report_jsonl(candidate):
key = ("file", str(candidate), None)
reports[key] = ("file", candidate, None)
return sorted(reports.values(), key=lambda report: (str(report[1]), report[2] or ""))
def iter_report_lines(kind: str, path: Path, member: str | None) -> Iterable[tuple[int, str]]:
if kind == "file":
with path.open("r", encoding="utf-8") as stream:
for line_number, line in enumerate(stream, start=1):
yield line_number, line
return
assert member is not None
with zipfile.ZipFile(path) as archive:
with archive.open(member) as binary_stream:
stream = io.TextIOWrapper(binary_stream, encoding="utf-8")
for line_number, line in enumerate(stream, start=1):
yield line_number, line
def result_from(event: dict) -> dict:
params = event.get("params")
if not isinstance(params, dict):
params = {}
result = params.get("result")
if not isinstance(result, dict):
result = event.get("result")
return result if isinstance(result, dict) else {}
def audit_reports(reports: list[tuple[str, Path, str | None]]) -> dict:
attempt_count = 0
retry_attempt_count = 0
status_counts: Counter[str] = Counter()
unexpected_statuses: Counter[str] = Counter()
error_result_count = 0
parse_error_count = 0
event_count = 0
report_names: list[str] = []
for kind, path, member in reports:
report_name = f"{path}!{member}" if member is not None else str(path)
report_names.append(report_name)
for line_number, raw_line in iter_report_lines(kind, path, member):
line = raw_line.strip()
if not line:
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
parse_error_count += 1
continue
if not isinstance(event, dict):
continue
method = event.get("method") or event.get("event") or event.get("type")
if method not in {"onTestBegin", "onTestEnd"}:
continue
event_count += 1
result = result_from(event)
if method == "onTestBegin":
attempt_count += 1
retry = result.get("retry", 0)
if isinstance(retry, (int, float)) and not isinstance(retry, bool) and retry > 0:
retry_attempt_count += 1
continue
status = result.get("status")
if status is None:
params = event.get("params")
status = params.get("status") if isinstance(params, dict) else event.get("status")
status_name = str(status) if status is not None else "unknown"
status_counts[status_name] += 1
if status_name not in EXPECTED_STATUSES:
unexpected_statuses[status_name] += 1
errors = result.get("errors")
if errors:
error_result_count += 1
violations: list[str] = []
if retry_attempt_count:
violations.append(f"retry_attempts={retry_attempt_count}")
if error_result_count:
violations.append(f"error_results={error_result_count}")
if unexpected_statuses:
violations.append(f"unexpected_statuses={dict(sorted(unexpected_statuses.items()))}")
if parse_error_count:
violations.append(f"parse_errors={parse_error_count}")
if not event_count:
violations.append("no_test_events")
return {
"reports": report_names,
"report_count": len(report_names),
"attempt_count": attempt_count,
"retry_attempt_count": retry_attempt_count,
"status_counts": dict(sorted(status_counts.items())),
"error_result_count": error_result_count,
"unexpected_statuses": dict(sorted(unexpected_statuses.items())),
"parse_error_count": parse_error_count,
"violations": violations,
}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Audit Playwright report-*.zip/report.jsonl artifacts for retries and errors."
)
parser.add_argument("paths", nargs="+", type=Path, help="report files, archives, or directories")
args = parser.parse_args(argv)
try:
reports = discover_reports(args.paths)
if not reports:
raise ValueError("no report-*.zip or report.jsonl artifacts found")
summary = audit_reports(reports)
except (FileNotFoundError, OSError, ValueError) as exc:
print(f"playwright-blob-audit: {exc}", file=sys.stderr)
return 2
print(json.dumps(summary, indent=2, sort_keys=True))
return 1 if summary["violations"] else 0
if __name__ == "__main__":
raise SystemExit(main())
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
import json
import subprocess
import sys
import tempfile
import unittest
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "playwright-blob-audit"
def event(method: str, result: dict) -> str:
return json.dumps({"method": method, "params": {"result": result}})
class PlaywrightBlobAuditTest(unittest.TestCase):
def run_audit(self, path: Path) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, str(SCRIPT), str(path)],
cwd=ROOT,
text=True,
capture_output=True,
check=False,
)
def test_passes_for_recursive_zip_report(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
archive_path = root / "nested" / "report-001.zip"
archive_path.parent.mkdir()
with zipfile.ZipFile(archive_path, "w") as archive:
archive.writestr(
"report.jsonl",
"\n".join(
[
event("onTestBegin", {"retry": 0}),
event("onTestEnd", {"status": "passed", "errors": []}),
event("onTestBegin", {"retry": 0}),
event("onTestEnd", {"status": "skipped", "errors": []}),
]
)
+ "\n",
)
result = self.run_audit(root)
self.assertEqual(result.returncode, 0, result.stderr)
summary = json.loads(result.stdout)
self.assertEqual(summary["report_count"], 1)
self.assertEqual(summary["attempt_count"], 2)
self.assertEqual(summary["retry_attempt_count"], 0)
self.assertEqual(summary["status_counts"], {"passed": 1, "skipped": 1})
def test_fails_for_retry_error_and_unexpected_status(self) -> None:
with tempfile.TemporaryDirectory() as directory:
report_path = Path(directory) / "report.jsonl"
report_path.write_text(
"\n".join(
[
event("onTestBegin", {"retry": 1}),
event("onTestEnd", {"status": "failed", "errors": [{"message": "boom"}]}),
]
)
+ "\n",
encoding="utf-8",
)
result = self.run_audit(report_path)
self.assertEqual(result.returncode, 1, result.stderr)
summary = json.loads(result.stdout)
self.assertEqual(summary["retry_attempt_count"], 1)
self.assertEqual(summary["error_result_count"], 1)
self.assertEqual(summary["unexpected_statuses"], {"failed": 1})
self.assertEqual(
summary["violations"],
["retry_attempts=1", "error_results=1", "unexpected_statuses={'failed': 1}"],
)
if __name__ == "__main__":
unittest.main()
+133 -3
View File
@@ -8,7 +8,8 @@
#
# Raw output:
# JSON object with:
# - pr: { number, branch, is_cross_repository, head_repository_owner,
# - pr: { number, branch, is_cross_repository, base_ref_name, base_head_oid,
# merge_base_oid, base_advanced_since_head, head_repository_owner,
# head_repository_name, head_ref_name, head_ref_oid, maintainer_can_modify }
# - since: { head_oid, committed_at } or null
# - checks: [{ type, name, workflow, status, conclusion, details_url, target_url, run_id, job_id }]
@@ -30,7 +31,8 @@
#
# Summary output (--summary):
# JSON object with:
# - pr: { number, branch, is_cross_repository, head_repository_owner,
# - pr: { number, branch, is_cross_repository, base_ref_name, base_head_oid,
# merge_base_oid, base_advanced_since_head, head_repository_owner,
# head_repository_name, head_ref_name, head_ref_oid, maintainer_can_modify }
# - since: { head_oid, committed_at } or null
# - failed_checks: [{ name, workflow, status, conclusion, run_id, job_id, details_url, target_url }]
@@ -148,9 +150,14 @@ fi
errors='[]'
pr_json='{}'
pr_view_succeeded=false
checks_json='[]'
checks_head_sha=''
checks_snapshot_complete=false
base_ref_name=''
base_head_oid=''
merge_base_oid=''
base_advanced_since_head=null
approval_required_runs_json='[]'
review_threads_json='[]'
hidden_unresolved_threads_json='[]'
@@ -670,6 +677,38 @@ fetch_head_commit_json() {
}'
}
fetch_base_ref_json() {
local owner=$1
local repo=$2
local pr_number=$3
gh_json api graphql \
-f query='
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
baseRefName
baseRefOid
}
}
}' \
-f owner="$owner" -f repo="$repo" -F pr="$pr_number" |
jq -c '{
base_ref_name: (.data.repository.pullRequest.baseRefName // null),
base_head_oid: (.data.repository.pullRequest.baseRefOid // null)
}'
}
fetch_merge_base_json() {
local owner=$1
local repo=$2
local base_head_oid=$3
local head_oid=$4
gh_json api "repos/$owner/$repo/compare/$base_head_oid...$head_oid" |
jq -c '{merge_base_oid: (.merge_base_commit.sha // null)}'
}
fetch_review_threads_nodes_json() {
local owner=$1
local repo=$2
@@ -761,7 +800,8 @@ comments_json='[]'
status_json='[]'
resolved_pr_number=''
is_cross_repository=null
if pr_view_json="$(gh_json pr view "$PR" --json number,headRefName,headRefOid,headRepositoryOwner,headRepository,maintainerCanModify,isCrossRepository,statusCheckRollup,comments,url)"; then
if pr_view_json="$(gh_json pr view "$PR" --json number,baseRefName,headRefName,headRefOid,headRepositoryOwner,headRepository,maintainerCanModify,isCrossRepository,statusCheckRollup,comments,url)"; then
pr_view_succeeded=true
resolved_pr_number="$(jq -r '.number // ""' <<<"$pr_view_json")"
if pr_url_owner="$(jq -r '.url | capture("github.com/(?<owner>[^/]+)/(?<repo>[^/]+)/pull/[0-9]+").owner // ""' <<<"$pr_view_json" 2>/dev/null)" && [[ -n "$pr_url_owner" ]]; then
repo_owner="$pr_url_owner"
@@ -771,6 +811,10 @@ if pr_view_json="$(gh_json pr view "$PR" --json number,headRefName,headRefOid,he
number: .number,
branch: (.headRefName // "unknown"),
is_cross_repository: (if has("isCrossRepository") then .isCrossRepository else null end),
base_ref_name: (.baseRefName // null),
base_head_oid: null,
merge_base_oid: null,
base_advanced_since_head: null,
head_repository_owner: (.headRepositoryOwner.login // null),
head_repository_name: (.headRepository.name // null),
head_ref_name: (.headRefName // null),
@@ -791,6 +835,43 @@ else
add_error "pr_view" "gh pr view failed"
fi
if [[ "$pr_view_succeeded" == "true" && -n "$repo_owner" && -n "$repo_name" && -n "$resolved_pr_number" ]]; then
if base_context_json="$(fetch_base_ref_json "$repo_owner" "$repo_name" "$resolved_pr_number")"; then
base_ref_name="$(jq -r '.base_ref_name // ""' <<<"$base_context_json")"
base_head_oid="$(jq -r '.base_head_oid // ""' <<<"$base_context_json")"
else
add_error "base" "gh api graphql base ref lookup failed"
fi
if [[ -n "$base_head_oid" && -n "$checks_head_sha" ]]; then
if merge_base_json="$(fetch_merge_base_json "$repo_owner" "$repo_name" "$base_head_oid" "$checks_head_sha")"; then
merge_base_oid="$(jq -r '.merge_base_oid // ""' <<<"$merge_base_json")"
else
add_error "merge_base" "gh api compare base and head failed"
fi
fi
if [[ -n "$base_head_oid" && -n "$merge_base_oid" ]]; then
if [[ "$base_head_oid" == "$merge_base_oid" ]]; then
base_advanced_since_head=false
else
base_advanced_since_head=true
fi
fi
pr_json="$(jq -c \
--arg base_ref_name "$base_ref_name" \
--arg base_head_oid "$base_head_oid" \
--arg merge_base_oid "$merge_base_oid" \
--argjson base_advanced_since_head "$base_advanced_since_head" \
'. + {
base_ref_name: (if $base_ref_name == "" then null else $base_ref_name end),
base_head_oid: (if $base_head_oid == "" then null else $base_head_oid end),
merge_base_oid: (if $merge_base_oid == "" then null else $merge_base_oid end),
base_advanced_since_head: $base_advanced_since_head
}' <<<"$pr_json")"
fi
if ! approval_required_runs_json="$(fetch_approval_required_runs_json "$repo_owner" "$repo_name" "$resolved_pr_number" "$checks_head_sha")"; then
approval_required_runs_json='[]'
add_error "approval_required_runs" "workflow runs API failed"
@@ -865,6 +946,55 @@ if [[ -n "$repo_owner" && -n "$repo_name" && -n "$resolved_pr_number" ]]; then
add_error "closing_head" "gh api graphql closing head commit failed"
fi
fi
# Re-read the base alongside the closing head. The base can advance while
# reviews and checks are collected, so the initial divergence fields are not a
# safe final snapshot.
if [[ -n "$repo_owner" && -n "$repo_name" && -n "$resolved_pr_number" ]]; then
if closing_base_context_json="$(fetch_base_ref_json "$repo_owner" "$repo_name" "$resolved_pr_number")"; then
closing_base_ref_name="$(jq -r '.base_ref_name // ""' <<<"$closing_base_context_json")"
closing_base_head_oid="$(jq -r '.base_head_oid // ""' <<<"$closing_base_context_json")"
closing_merge_base_oid=''
comparison_head_oid="$closing_head_oid"
[[ -n "$comparison_head_oid" ]] || comparison_head_oid="$checks_head_sha"
if [[ -n "$closing_base_head_oid" && -n "$comparison_head_oid" ]]; then
if closing_merge_base_json="$(fetch_merge_base_json "$repo_owner" "$repo_name" "$closing_base_head_oid" "$comparison_head_oid")"; then
closing_merge_base_oid="$(jq -r '.merge_base_oid // ""' <<<"$closing_merge_base_json")"
else
add_error "closing_merge_base" "gh api compare base and head failed after evidence collection"
fi
fi
base_ref_name="$closing_base_ref_name"
base_head_oid="$closing_base_head_oid"
merge_base_oid="$closing_merge_base_oid"
base_advanced_since_head=null
if [[ -n "$base_head_oid" && -n "$merge_base_oid" ]]; then
if [[ "$base_head_oid" == "$merge_base_oid" ]]; then
base_advanced_since_head=false
else
base_advanced_since_head=true
fi
fi
else
add_error "closing_base" "gh api graphql base ref lookup failed after evidence collection"
base_ref_name=''
base_head_oid=''
merge_base_oid=''
base_advanced_since_head=null
fi
pr_json="$(jq -c \
--arg base_ref_name "$base_ref_name" \
--arg base_head_oid "$base_head_oid" \
--arg merge_base_oid "$merge_base_oid" \
--argjson base_advanced_since_head "$base_advanced_since_head" \
'. + {
base_ref_name: (if $base_ref_name == "" then null else $base_ref_name end),
base_head_oid: (if $base_head_oid == "" then null else $base_head_oid end),
merge_base_oid: (if $merge_base_oid == "" then null else $merge_base_oid end),
base_advanced_since_head: $base_advanced_since_head
}' <<<"$pr_json")"
fi
if [[ -n "$opening_head_oid" && -n "$closing_head_oid" && "$opening_head_oid" != "$closing_head_oid" ]]; then
add_error "review_evidence" "head changed during evidence collection"
fi
+71 -2
View File
@@ -79,6 +79,20 @@ if [[ "$1" == "repo" && "$2" == "view" ]]; then
exit 0
fi
if [[ "$1" == "api" && "$2" == repos/kdlbs/kandev/compare/* ]]; then
merge_base="${GH_MERGE_BASE:-base-branch-sha}"
if [[ "${GH_BASE_SEQUENCE:-stable}" == "race" && "$2" == *"late-base-head-sha..."* ]]; then
merge_base="late-merge-base-sha"
fi
cat <<JSON
{
"base_commit": { "sha": "${GH_BASE_HEAD:-base-head-sha}" },
"merge_base_commit": { "sha": "$merge_base" }
}
JSON
exit 0
fi
if [[ "$1" == "api" && "$2" == "repos/kdlbs/kandev/pulls/comments/111" ]]; then
cat <<'JSON'
{
@@ -170,6 +184,7 @@ if [[ "$1" == "pr" && "$2" == "view" && "$4" == "--json" ]]; then
cat <<JSON
{
"number": 123,
"baseRefName": "${GH_BASE_REF_NAME:-main}",
"headRefName": "feat/pr-state",
"headRefOid": "${GH_CHECKS_HEAD:-abc123}",
"headRepositoryOwner": { "login": "${GH_HEAD_REPOSITORY_OWNER:-kdlbs}" },
@@ -524,7 +539,29 @@ if [[ "$1" == "api" && "$2" == "--paginate" && "$3" == "-X" && "$4" == "GET" &&
exit 0
fi
if [[ "$1" == "api" && "$2" == "graphql" ]]; then
if [[ "$1" == "api" && "$2" == "graphql" ]]; then
if [[ "$*" == *"baseRefOid"* && "$*" != *"headRefOid"* ]]; then
base_head="${GH_BASE_HEAD:-base-head-sha}"
if [[ "${GH_BASE_SEQUENCE:-stable}" == "race" ]]; then
if [[ -f "${GH_BASE_COUNTER_FILE:?}" ]]; then
base_head="late-base-head-sha"
else
: >"$GH_BASE_COUNTER_FILE"
fi
fi
printf '%s\n' '{
"data": {
"repository": {
"pullRequest": {
"baseRefName": "main",
"baseRefOid": "'"$base_head"'"
}
}
}
}'
exit 0
fi
if [[ "$*" == *"headRefOid"* ]]; then
head_oid="abc123"
if [[ "${GH_HEAD_SEQUENCE:-stable}" == "race" ]]; then
@@ -701,6 +738,7 @@ test_snapshot_happy_path() {
assert_jq "pr number" '.pr.number == 123' "$json"
assert_jq "branch" '.pr.branch == "feat/pr-state"' "$json"
assert_jq "head delivery target" '.pr.head_repository_owner == "kdlbs" and .pr.head_repository_name == "kandev" and .pr.head_ref_name == "feat/pr-state" and .pr.head_ref_oid == "abc123" and .pr.maintainer_can_modify == true' "$json"
assert_jq "base divergence fields" '.pr.base_ref_name == "main" and .pr.base_head_oid == "base-head-sha" and .pr.merge_base_oid == "base-branch-sha" and .pr.base_advanced_since_head == true' "$json"
assert_jq "since timestamp" '.since.committed_at == "2026-06-01T12:00:00Z"' "$json"
assert_jq "checks collapse duplicate workflow attempts" '.checks | length < 10' "$json"
assert_jq "latest duplicate check uses newest attempt" '[.checks[] | select(.name == "web lint")][0] | .conclusion == "success" and .run_id == "27340000001"' "$json"
@@ -1142,10 +1180,12 @@ test_graphql_failure_records_error_but_keeps_other_data() {
assert_jq "reviews still present on graphql failure" '.reviews | length == 2' "$json"
assert_jq "review threads empty on graphql failure" '.review_threads == []' "$json"
assert_jq "unresolved count unknown on graphql failure" '.unresolved_review_thread_count == null' "$json"
assert_jq "graphql failure records opening and closing head errors" '.errors | length == 3' "$json"
assert_jq "graphql failure records base, opening, and closing errors" '.errors | length == 5' "$json"
assert_jq "graphql failure records base lookup error" '.errors[] | select(.source == "base") | .message == "gh api graphql base ref lookup failed"' "$json"
assert_jq "graphql failure records since error" '.errors[] | select(.source == "since") | .message == "gh api graphql head commit failed; including historical comments"' "$json"
assert_jq "graphql failure records review_threads error" '.errors[] | select(.source == "review_threads") | .message == "gh api graphql reviewThreads failed"' "$json"
assert_jq "graphql failure records closing head error" '.errors[] | select(.source == "closing_head") | .message == "gh api graphql closing head commit failed"' "$json"
assert_jq "graphql failure records closing base error" '.errors[] | select(.source == "closing_base") | .message == "gh api graphql base ref lookup failed after evidence collection"' "$json"
assert_jq "since fallback includes all historical comments" '.issue_comments | length == 8' "$json"
pass "graphql failure records error but keeps other data"
}
@@ -1196,6 +1236,7 @@ test_summary_mode_returns_compact_fixup_state() {
assert_jq "summary keeps pr" '.pr.number == 123' "$json"
assert_jq "summary keeps authoritative head delivery target" '.pr.head_repository_owner == "kdlbs" and .pr.head_repository_name == "kandev" and .pr.head_ref_name == "feat/pr-state" and .pr.head_ref_oid == "abc123" and .pr.maintainer_can_modify == true' "$json"
assert_jq "summary keeps base divergence fields" '.pr.base_ref_name == "main" and .pr.base_head_oid == "base-head-sha" and .pr.merge_base_oid == "base-branch-sha" and .pr.base_advanced_since_head == true' "$json"
assert_jq "summary keeps since" '.since.committed_at == "2026-06-01T12:00:00Z"' "$json"
assert_jq "summary has no approval-required runs by default" '.approval_required_runs == []' "$json"
assert_jq "summary failed check count" '.failed_checks | length == 3' "$json"
@@ -1234,6 +1275,32 @@ test_summary_reports_current_head_fork_approval_runs() {
pass "--summary reports current-head fork approval runs"
}
test_summary_reports_base_not_advanced_when_head_matches_merge_base() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_BASE_HEAD=base-branch-sha GH_MERGE_BASE=base-branch-sha PATH="$tmp/bin:$PATH" "$SCRIPT" --summary 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "summary reports base not advanced" '.pr.base_advanced_since_head == false' "$json"
pass "summary reports base not advanced when head matches merge base"
}
test_summary_revalidates_base_at_closing_head() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_BASE_SEQUENCE=race GH_BASE_COUNTER_FILE="$tmp/base-calls" GH_MERGE_BASE=base-head-sha PATH="$tmp/bin:$PATH" "$SCRIPT" --summary 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "summary uses closing base ref" '.pr.base_head_oid == "late-base-head-sha" and .pr.merge_base_oid == "late-merge-base-sha" and .pr.base_advanced_since_head == true' "$json"
pass "summary revalidates base at closing head"
}
test_summary_reports_approval_run_fetch_failure() {
local tmp
make_tmp_dir tmp
@@ -1448,6 +1515,8 @@ test_graphql_pagination_collects_all_threads
test_all_flag_includes_historical_comments_and_reviews
test_summary_mode_returns_compact_fixup_state
test_summary_reports_current_head_fork_approval_runs
test_summary_reports_base_not_advanced_when_head_matches_merge_base
test_summary_revalidates_base_at_closing_head
test_summary_reports_approval_run_fetch_failure
test_summary_all_flag_includes_historical_unresolved_threads
test_job_log_mode_emits_bounded_failure_context