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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions scripts/sweep_workspace_staleness.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@
# Branch-name prefixes that are machinery, not someone's stranded work.
IGNORED_BRANCH_PREFIXES = ("gh-readonly-queue/", "dependabot/", "l10n_")

# GitHub rejects an issue body over 65536 characters; keep the table readable
# long before that and guard the total as a last resort.
MAX_REMOTE_ROWS = 40
MAX_BODY_CHARS = 60000

# Remote classification outcomes worth asserting in tests.
DEFAULT = "default"
OPEN_PR = "open-pr"
Expand Down Expand Up @@ -350,15 +355,31 @@ def render(
"",
]
if remote_rows:
ordered = sorted(remote_rows, key=lambda item: (-item["age_days"], item["repo"]))
shown = ordered[:MAX_REMOTE_ROWS]
lines.append(
f"{len(ordered)} stranded branch(es) past the cutoff"
+ (f" (oldest {MAX_REMOTE_ROWS} shown)." if len(ordered) > MAX_REMOTE_ROWS else ".")
)
lines.append("")
lines.append("| Repository | Stranded branch | Last commit | Age (days) |")
lines.append("|---|---|---|---|")
for row in sorted(
remote_rows, key=lambda item: (-item["age_days"], item["repo"])
):
for row in shown:
lines.append(
f"| {row['repo']} | [{row['branch']}]({row['url']}) "
f"| {row['last_commit']} | {row['age_days']} |"
)
hidden = len(ordered) - len(shown)
if hidden:
per_repo: dict[str, int] = {}
for row in ordered[MAX_REMOTE_ROWS:]:
per_repo[row["repo"]] = per_repo.get(row["repo"], 0) + 1
summary = ", ".join(f"{name} ({count})" for name, count in sorted(per_repo.items()))
lines.append("")
lines.append(
f"+{hidden} older stranded branch(es) not listed: {summary}. "
"The full list is in the run log."
)
lines.append("")
lines.append(
"A stranded branch is not the default branch, has no open pull "
Expand Down Expand Up @@ -400,7 +421,17 @@ def render(
if run_url:
lines.append("")
lines.append(f"Produced by run {run_url}.")
return "\n".join(lines)
body = "\n".join(lines)
# GitHub rejects issue bodies over 65536 characters; the first scheduled
# run died exactly there (2026-08-22, GraphQL "Body is too long"). The
# table cap above bounds the usual cause; this guard bounds every cause.
if len(body) > MAX_BODY_CHARS:
note = (
f"\n\n---\n[truncated at {MAX_BODY_CHARS} of {len(body)} characters "
"to fit the GitHub issue limit; full output is in the run log.]"
)
body = body[: MAX_BODY_CHARS - len(note)] + note
return body


def main(argv: list[str] | None = None) -> int:
Expand Down
34 changes: 34 additions & 0 deletions tests/test_sweep_workspace_staleness.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,37 @@ def test_render_reports_unreadable_private_repositories():
body = render([ROW], ["OpenAdaptAI/private-one"], None, [], 14, "")
assert "OpenAdaptAI/private-one" in body
assert "OA_SWEEP_TOKEN" in body


def _rows(count: int) -> list[dict]:
return [
dict(ROW, branch=f"feat/old-{index}", age_days=100 - index)
for index in range(count)
]


def test_render_caps_remote_table_and_counts_the_rest():
from sweep_workspace_staleness import MAX_REMOTE_ROWS

rows = _rows(MAX_REMOTE_ROWS + 7)
body = render(rows, [], None, [], 14, "")
assert f"{MAX_REMOTE_ROWS + 7} stranded branch(es) past the cutoff" in body
assert f"oldest {MAX_REMOTE_ROWS} shown" in body
assert "+7 older stranded branch(es)" in body
assert "+7 older stranded branch(es)" in body
# Exactly the cap is listed; nothing hidden leaks into the body.
assert body.count("feat/old-") == MAX_REMOTE_ROWS
assert "feat/old-40" not in body


def test_render_body_never_exceeds_github_limit():
from sweep_workspace_staleness import MAX_BODY_CHARS

wide = [
dict(ROW, branch="feat/" + "x" * 400 + str(index), url="https://e.example")
for index in range(200)
]
body = render(wide, [], None, [], 14, "https://run.example")
assert len(body) <= 65536
if len(body) > MAX_BODY_CHARS:
assert "[truncated at" in body