Skip to content

feat: keep an opt-in problem history for the Briefing - #165

Merged
Svilen-Stefanov merged 13 commits into
mainfrom
feat/problem-history
Nov 8, 2026
Merged

Svilen-Stefanov merged 13 commits into
mainfrom
feat/problem-history

Conversation

@Svilen-Stefanov

@Svilen-Stefanov Svilen-Stefanov commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

What

A new input, problem_history, off by default. When a repository sets it to 'true', every sync run (a push to the analysed branch) keeps that repository's problem history for the CodeBoarding Briefing:

  1. It reads the current problems.json from the orphan codeboarding/briefing branch in the same repository.
  2. It collects what changed since then with the run's own token: issues updated since the last run (on the first run, open issues plus those closed in the last 90 days), PRs merged into the analysed branch, their changed paths and approvers, and each merged PR's architecture result from its review artifact.
  3. It sends that to the CodeBoarding service (POST /v1/briefing, licensing-aws chore(main): release 1.7.0 #51), which returns the new ledger. All rules live in the service; this repository only collects and commits.
  4. It commits the ledger to codeboarding/briefing: one commit per update, never merged, runs no workflow. If another run moved the branch first, it rebuilds once on top of that.

Nothing is sent or written without the input. The README's new "Problem history" section shows the permissions the sync job needs (contents: write, issues: read, pull-requests: read, actions: read) and exactly what is sent and written. The step never fails the run.

Tested

  • 324 tests pass on Python 3.12; 27 are new in tests/test_problem_history.py. Black and actionlint are clean.
  • Run for real against CodeBoarding/CodeBoarding-evals: the first run created the orphan branch with 3 problems (endpoint on dev); a second run fast-forwarded it with 0 new problems.

Merging this does not move @v1: release-please holds the release until its release PR is merged. Release it only after #51 is in production.

Nothing visible changes in this repository; what the Briefing shows is on webview #281.

Review fixes

  • Issue bodies are always cut to 2000 characters.
  • First runs cap open and recently closed issues separately (1000 each), and issuesThrough never claims issues a cap skipped.
  • Each run sends the repository's open issue numbers, so the service can drop issues that are gone.
  • Findings a run missed are retried later (10 per run), and open issues with an open linked pull request are re-read each run (100 per run), so a fix moving from draft to approved shows.
  • Work is bounded: the script stops itself after 10 minutes (timeout-minutes is not allowed on a composite action's steps), artifacts over 50 MB are skipped, the merged scan is capped, and the service call times out at 300 s.
  • A ruleset or name clash on the branch gives a clear notice instead of a retry.
  • 345 tests pass; run end to end against the dev service on CodeBoarding-evals.

Second review fixes

  • Never sends the run's GITHUB_TOKEN: without id-token: write the update is skipped with a notice. The input description and README list all five permissions.
  • A bulk issue edit can no longer stall the history; every issue with an open linked pull request gets a turn to be re-read; past 500 merges the oldest are sent first, with a note.
  • Findings are read only from this repository's own review runs with a matching metadata.json, so a fork cannot plant them; retries stop when the review artifact has expired (14 days); artifact entries over 1 MB are not read; an oversized request drops the largest findings instead of the whole update.
  • About 15 GitHub requests per sync for a repository with 2000 open issues (was about 35), 46 at worst (was about 125). The open issue numbers are listed once a day.
  • Shares the GitHub call, error message and packing with closed_features.py.
  • 343 tests pass. Run against the dev service on CodeBoarding-evals; without OIDC it skips and sends nothing.

🤖 Generated with Claude Code

With `problem_history: 'true'`, each sync run collects the repository's
issues and merged pull requests since the last update, plus each merged
pull request's architecture findings from its review artifact, sends
them to POST /v1/briefing and commits the returned ledger as
problems.json on the orphan codeboarding/briefing branch. Every rule
lives in the service; the Action only gathers facts and writes the
answer. Off by default, and it never fails the run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@codeboarding-review

codeboarding-review Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

CodeBoarding review

Status: 1 changed component

See the full change in CodeBoarding.

graph LR
    n_action_support_scripts["action_support_scripts"]
    classDef added fill:#1f883d,stroke:#0b5d23,color:#ffffff;
    classDef modified fill:#bf8700,stroke:#7d4e00,color:#ffffff;
    classDef deleted fill:#cf222e,stroke:#82071e,color:#ffffff,stroke-dasharray:5 3;
    class n_action_support_scripts modified;
Loading

download artifacts · run 42433085873

Svilen-Stefanov and others added 12 commits November 7, 2026 17:40
- Cut every issue body to 2000 UTF-16 code units, never splitting a
  surrogate pair; the oversize-only trimming is gone.
- Stay under the service's caps by construction: 1000 open plus 1000
  recently closed issues on a first run, 1000 plus 100 re-read later,
  500 merged, 40 new plus 10 retried findings.
- Send openIssues: every open issue number, read first in creation
  order, complete false past 5000, after a capped issue read, and on
  the conflict retry.
- Ask again for findings of the 10 newest ledger merges whose findings
  were never read; send skipped envelopes so they count as read.
- Re-read up to 100 stored open issues with an open linked pull request.
- First-run caps are per pass, and issuesThrough never claims an issue
  a cap skipped.
- Skip review artifacts over 50 MB, scan at most 20 pages of merged
  pull requests, give the service call 300 s, and stop the script after
  10 minutes (a composite step cannot set timeout-minutes).
- A ruleset or branch-name 422 on the ref update is a clear notice, not
  retried; a second lost race is a notice too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Page a pull request's changed files through the shared `_nodes` pager,
  starting from the page MERGED_QUERY already returned, instead of a
  second hand-written cursor loop; `islice` keeps the 300-path cap from
  asking for a page past it.
- Build issue, linked pull request and merged pull request facts as one
  literal each, with an `_author` spread for the optional login.
- Pick a timeline event's pull request through one side table.
- Read the ledger's ref and file under one 404 guard, and drop the
  unreachable assertion after the retry loop.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- FakeGitHub gains add_review() for a run plus its review artifact and
  calls() for the GraphQL requests a test inspects; ledger(), numbers()
  and closed_node() replace literals repeated across tests.
- Table-driven: the surrogate-safe cut, the 5000 open-issue boundary,
  first-run caps, read_architecture, main's never-fail outcomes and the
  three commit shapes (orphan, fast-forward, push-named).
- The changed-paths test now also asserts no page past the 300-path cap
  is requested; the cap test asserts the uncapped run's issues.

The new file passes against the code before the refactor, and the old
file passes against the code after it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep what a customer needs (opt-in, what is sent and its limits, what
is written, permissions, never failing the run, how to turn it off)
and drop the restatements: the Briefing's wording when it is off, and
where the problem rules live, which the module docstring already says.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…d_features

problem_history had its own copies of the GitHub request, the error-body
message and the gzip-and-check step. closed_features now takes a method,
payload, API base, token and timeout on _github, exposes _error_message
and _gzip, and names what _check_size measured; problem_history uses them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A later run cut at 1000 issues recorded the last one read as issuesThrough.
When more than 1000 issues share one update time, the next run starts on
the same page and reads it again forever. A capped run now records at
least the previous issuesThrough plus the overlap and a second, so each
run moves on; ties left past the cap are lost.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ead again

Re-reads picked the 100 issues whose stored pull request moved most
recently, so those were read every run and the rest never were. They now
take turns in number order, starting where the previous update stopped,
which the commit count of codeboarding/briefing tells. The ledger head is
read with that count in one GraphQL query instead of the REST ref.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…read

Past 500 merged pull requests the scan stopped at the first 500 in update
order, newest first, and said nothing. The service records the newest
merge it is sent as where the next run starts, so the older ones were
never read. A capped run now sends the 500 oldest, notes the cut, and the
next run carries on from there. The README states the cap.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…own reviews, within bounds

Retrying unread findings could cost tens of requests a run for nothing:
every unread merge of 56 days was tried, any workflow's runs on its head
commit were listed, and each head commit took its own pulls call. Retries
now stop at the review artifact's 14-day retention, list only CodeBoarding
runs of pull_request events, and read the heads in one aliased query.

A fork's pull_request run executes the fork's workflow, so it could upload
a forged review artifact. Findings now come only from runs whose head
repository is this one, and only when metadata.json names the pull
request. metadata.json and closed/architecture.json are not read past
1 MB each, and an envelope that would take the request over the service's
limit is left out (largest first) instead of skipping the whole update.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Without id-token: write, _bearer falls back to the run's GITHUB_TOKEN,
which on the sync job holds contents: write. The problem history now
skips the update with a notice to add id-token: write, before it reads
anything. The input description, the README permissions and the failure
hint list all five permissions, and the hint also fires when GraphQL
refuses the repository or one of its lists (FORBIDDEN or
INSUFFICIENT_SCOPES), which used to surface as a bare error.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The full list costs a request per 100 open issues on every run, 20 for a
repository with 2000, and only lets the service drop an issue that was
deleted, transferred or made a discussion. It is now read by the first
update of each UTC day (the ledger's updatedAt says which that is), and
the other runs send it as incomplete, which drops nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@Svilen-Stefanov
Svilen-Stefanov marked this pull request as ready for review November 8, 2026 01:15
@Svilen-Stefanov
Svilen-Stefanov merged commit b5ae0c1 into main Nov 8, 2026
5 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 8, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-08T01:20:49.541573Z ab8a13a Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab8a13a8e1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +407 to +409
# A bulk edit can give more issues than the cap one update time, and stopping at it would
# start every later run on the same page. Moving past the overlap loses ties past the cap.
stop = max(last or since, since + ISSUE_OVERLAP + timedelta(seconds=1))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve issues sharing the capped update timestamp

When more than MAX_ISSUES issues have the same updatedAt value, the first capped pass records that timestamp, and the next pass rereads the same earliest 1,000 issues because of the overlap. This max(...) then advances issuesThrough beyond the shared timestamp, so every remaining issue in that bulk update is permanently skipped rather than being picked up by later syncs. The continuation needs a stable tie-breaker or another way to retain progress without moving past unread timestamp ties.

Useful? React with 👍 / 👎.

Comment on lines +490 to +493
for n in _nodes(gh, MERGED_QUERY, variables, "pullRequests", pages=MAX_MERGED_PAGES):
if n is MORE:
notes.append(f"scanned the {MAX_MERGED_PAGES * 50} latest updated pull requests only")
break

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Continue merged-PR scans across the page cap

If a bulk edit makes more than 1,000 historical merged PRs newer in updatedAt order than a recently merged PR, this branch stops before reaching the new merge. Because no scan cursor is persisted, every later sync starts from page one and examines the same 1,000 PRs, so the hidden merge and its architecture findings may never enter the history. Persist a continuation point or otherwise ensure a capped scan eventually visits later pages.

Useful? React with 👍 / 👎.

Comment on lines +298 to +303
closer = next(
(
t["closer"]
for t in timeline
if (t or {}).get("__typename") == "ClosedEvent"
and (t.get("closer") or {}).get("__typename") == "PullRequest"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select the most recent pull request that closed the issue

For an issue that was closed by PR A, reopened, and later closed by PR B, the timeline contains both ClosedEvents in chronological order, but next(...) selects PR A. The service therefore receives an obsolete closedBy relationship and can attribute the eventual fix to the wrong change; choose the latest qualifying closure event instead.

Useful? React with 👍 / 👎.

Comment on lines +561 to +562
listed = gh.json(f"/repos/{repo}/actions/runs?head_sha={quote(sha)}&event=pull_request&status=success&per_page=30")
runs = [r for r in listed.get("workflow_runs") or [] if _own_review(r, repo)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Paginate workflow runs before searching for review artifacts

When a head SHA has more than 30 successful pull_request workflow runs and its CodeBoarding run is not on the first page, this single request never sees it. Subsequent retries repeat the same first page until artifact retention expires, so the merge's architecture findings are silently omitted even though a valid artifact exists. Paginate the workflow-run results until the CodeBoarding candidates are found or the intended search limit is reached.

Useful? React with 👍 / 👎.

Comment on lines +608 to +609
retries = newest(m for m in _unread(ledger) if (_parse(m.get("mergedAt")) or retained) > retained)
retries = retries[:MAX_FINDINGS_RETRIES]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rotate artifact retries so older merges are not starved

When more than MAX_FINDINGS_RETRIES ledger entries remain unread, this always retries the newest ten. If any of those entries has no usable artifact, it remains unread and is selected again on every sync, while older entries wait behind it and reach the 14-day retention cutoff first. This is especially likely after a run processes more than 40 merges, and it can prevent valid artifacts for older merges from ever being inspected; persist retry progress or rotate through the unread set.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant