Repository navigation
Agent-friendly constraints plan #877
Description
Clustering summary
Recurring agent mistakes this period are not random review nits. They are a few ways a fixed clock, a platform probe, or a teardown race reports the wrong failure. The highest-leverage fixes are APIs and fixtures that make that wrong report unreachable. Lint and CI cover reintroduction. Prose is only the residue a check cannot see.
Scan, and the denominators used below:
- 80 most recently merged PRs (
gh pr list --state merged --limit 80), 2026-10-04 through 2026-10-16. 40 recently closed PRs overlap that set; test: fix #829 flakes at the source and cut fixed test waits #855, fix(web): show image tool results instead of empty success #874, and fix(web): keep provider failures visible after the run goes idle #875 were still open when scanned. - Requested items Flaky CI tests: session-history E2E anchor, Windows git-info refresh, Windows setup-config lock timeout #829, test: fix root causes of two flaky tests (#829) #831, test: fix #829 flakes at the source and cut fixed test waits #855, test: close the five highest-value gaps from the test audit #857, Add contributor-only test-value skill #860, fix: stop three CI flakes from treating clocks as readiness #861, fix(setup): use a heartbeat lease for the config lock #863, fix(terminal): lay out sustained output incrementally #869, fix(web): keep streaming updates independent of history length #870, fix(web): bound session catalog and history read cost #871, fix(web): re-render only the live transcript row while streaming #873, fix(web): show image tool results instead of empty success #874, fix(web): keep provider failures visible after the run goes idle #875: PR or issue body, commits, and GraphQL
reviewThreads(first: 100). - 163 inline review comments from
GET /repos/openpi-dev/openpi/pulls/comments(updated descending, paginated). Newest is 2026-10-12, oldest 2026-08-24. - 100 newest
mainworkflow runs: 68 success, 24 cancelled, 8 failure.ci.ymlsetsconcurrency.cancel-in-progress: true, so the cancellations are superseded runs, not flakes. git log --since=2026-09-15is 563 commits, plusgit log -Sforcscript,//B, andunrouteAll.
The Oct 14–16 PRs in the requested set have zero review threads and do not appear in the 163 inline comments. This week's evidence is commit messages, PR bodies, and CI logs.
Of the 8 red main runs, two are Pages (unpinned actions on 37187211113; environment protection on 37187193606). On 38736671833 and 37883190349, the Node and Background terminals jobs are gate steps after UI_RESULT=failure (job logs read). They are not extra product failures.
| Category | Distinct merged PRs / 80 | Still open in tree? |
|---|---|---|
Playwright route teardown vs in-flight route.fetch() |
1 (#863); also open #855 | Yes. 23 of 26 page.route / context.route e2e files never call unrouteAll |
Windows process-start probes (//B, UTF-16, PowerShell) |
2 (#857, #863) | The probe is gone (heartbeat). //B can be reintroduced |
Async UI read on the product's timer (findBy* / getByRole) |
2 (#861, #863); open #855 widened timeouts instead | Workaround is local to one spec |
| Startup time charged to a fixed deadline | 2 (#831, #861) | Sandbox and one git fixture are fixed inline |
| Canonical path vs substring or sanitized text | 2 (#705, #857) | Provenance compares realpath. Display text and path identity are still the same string in the #705 shape |
| Green by rerun, empty retrigger commit, or a wider timeout | 3 (#707, #718, #857); open #855 | No vitest retry today. Empty retrigger commits are not rejected |
| Drag trailing click on a 250ms wall clock | 1 (#861); open #855 | Eliminated by a one-click flag plus a regression |
#869, #870, #871, #873, #874, and #875 are product or cost fixes and do not add another pitfall class. #860 lands the test-value skill, which already says retries, wider timeouts, and refreshed snapshots must not conceal a regression.
Three main failures in the 100 runs did not repeat often enough to cluster: a spinner frame mismatch (⠹ vs ⠸) on 37185879432, git-info process output -1 !== 7 on 37172625519, and a Windows EBUSY rmdir of a temp fixture on 36023380925 (same run as the git-info cold start).
A local prototype on unpushed scratch/agent-constraints-prototypes (fd8b808, not a PR) checked the mechanisms below. It is not part of this repo's main.
Plan
1. Drain Playwright routes in one fixture
- Category: teardown racing an in-flight route handler (
Response has been disposed). - Evidence: 947e36e on #863. The PR body also cites run 38774705615 (log not re-downloaded here). Open #855 added the same
unrouteAll({ behavior: "wait" })drain.tests/web/openpi-web.e2e.tsnow drains inafterEach. The prototype listed 23 other route files with nounrouteAllat all, includingsession-history.e2e.ts,settings-parity.e2e.ts, andworkbench-polish.e2e.ts.tests/web/playwright.config.tshas no global drain. - Constraint: One Playwright fixture owns
afterEachand callspage.unrouteAll({ behavior: "wait" })unless the page is already closed. A CI grep fails anytests/web/*.e2e.tsthat callspage.route(orcontext.route(and does not containunrouteAll. The prototype's grep matched those 23 files and ignored a comment-only//Bstyle false pattern for the sibling check. - Pyramid: codebase (fixture), with a CI grep so a new spec cannot skip it.
- Effort: M.
- Risk: A handler that never finishes hangs the teardown. The existing comment in
openpi-web.e2e.tsis the right bound: wait for in-flight handlers, and still fail the test if the handler throws for any other reason. Do not raise the test timeout to hide a stuck route.
2. Keep process liveness on the heartbeat, and ban //B
- Category: per-platform process queries.
cscript //Bswallowed a JScript syntax error, so a broken WMI query looked like empty output and the lock waited out 5s. The same PR then chased UTF-16 and a PowerShell cold start. - Evidence: five commits on #857: 297e581 (drop PowerShell for cscript), 0d693e0 (UTF-16), b03b1df (PowerShell fallback), ea256c6 (WMI only), fe57ee4 (
//Bhid the syntax error; args were["//Nologo", "//B", script, pid]). #863 deletes the probe and uses a heartbeat lease (#862). #829 records the Windows lock timeout on run 38558753554. 2 of 80 merged PRs. - Constraint: Liveness stays the lock-file heartbeat (
futimeson the holder's fd). A CI grep fails a string literal"//B"or'//B'anywhere underextensions/andscripts/. The prototype matched line 1285 offe57ee4^:extensions/shared/setup-config.ts, matched nothing in currentsetup-config.ts, and did not match a comment or a URL that merely contains the characters//B. - Pyramid: codebase (the heartbeat is already the API). CI grep is the reintroduction stop. A custom lint is not a better layer than that grep.
- Effort: S.
- Risk: Low. The literal form is the footgun. A comment that says
"//B"insideextensions/would fail the grep; that is an acceptable false positive.
3. Read UI that publishes on a timer from a check-phase helper
- Category:
findByRole/waitForand the product timer share a phase, so a stalled Windows runner reports a missing README or burns the test budget. SyncgetByRolethen fails before React commits. The rejected fix was a larger timeout. - Evidence: 2f6bd28 on #863 adds
paintedRoleintests/web/workbar-session-position.spec.ts. #861 cites composer-context-navigation timing out on run 38850034958. Main UI log 37883190349:Unable to find role="button" and name "README.md"andTest timed out in 20000msinworkbar-session-position.spec.ts. 36881409881:session-sidebar-actions.spec.ts:376timed out in 5000ms. Open #855 proposedasyncUtilTimeout: 5_000andtestTimeout: 15_000. That bump is not onmain(git log -S asyncUtilTimeoutis empty). 2 of 80 merged PRs, plus test: fix #829 flakes at the source and cut fixed test waits #855. - Constraint: One helper in the UI test setup: flush a check-phase turn (
actplus a macrotask), thenqueryByRole, and callgetByRoleonly after the existing 1s budget so absence still fails. Specs that follow a timer-published list or review snapshot use that helper instead offindByRole/waitFor. Do not raisetestTimeoutor Testing Library's async timeout for this class. - Pyramid: test helper. A lint that bans
getByRoleorfindByRolewill false-positive on nodes that are already committed. That choice stays prose, and the test-value skill already says a wider timeout does not supply a missing phase. - Effort: M.
- Risk: Medium. A helper that always waits 1s slows the suite if callers use it for synchronous nodes. Keep the fast path (
queryByRoleon the first turn) and do not import it into tests that are not waiting on a timer publish.
4. Arm a deadline only after its precondition
- Category: a fixed budget that includes process start. The sandbox armed its 1s non-yielding-script watchdog before the child was listening, so a slow
node --permissionspawn was reported asScript execution timed out. The git-info fixture copiednode.exetogit.exeand timed the first, cold exec against the 3s product budget. - Evidence: 20a7b98 on #861. The PR body cites run 38848560632. Current
extensions/workflows/sandbox.tsarms the watchdog insidebeginExecution, which runs on{ kind: "listening" }. Git cold start: 8ed7d566 on #831, and the same assertion on main run 36023380925 (Timed out waiting for local git info, 6436ms,tests/extensions/git-info/index.test.ts). #829 cites the later Windows git-info run 38550635790. 2 of 80 merged PRs. - Constraint: The sandbox shape is the invariant: spawn, wait until the child says it is listening, then arm the script budget. A copied-binary fixture launches once untimed before any assertion clock. There is no shared type that expresses "this number starts later" for arbitrary
setTimeoutcalls. - Pyramid: codebase for the sandbox (landed). Test helper for cold executables (landed in that one file). A new watchdog that does not use this order cannot be banned by a lint without a false-positive flood.
- Effort: S for a regression that the listening message precedes
armWatchdog(the sandbox test from fix: stop three CI flakes from treating clocks as readiness #861 is the start). No new abstraction. - Risk: Low if the work stops at the regression. Extracting a generic
armAfterReadyhelper would be unused ceremony. - Prose residue: "A deadline starts when the thing it measures has started." The test-value skill should keep that sentence only if a later change shows agents still arm timers early. It is not enforceable, and this plan does not edit the skill.
5. Separate a canonical path from display text
- Category: a directory-name substring, a Windows 8.3 path, or sanitized display text used as the file identity.
- Evidence: 924d651 and e3df017 on #857.
scripts/provenance.mjsnow comparesrealpathSync.nativeand the package name, not a folder namedopenpi. Review on #705 (2026-10-09, the only October inline review in the 163 that is this class):sanitizeTerminalTextwas applied topath/resolvedPath, so the preview opened the cleaned string. 2 distinct PRs. The other 22 "path" review comments in the 163 are different defects and are not counted here. - Constraint:
evaluateOpenPiProvenancestays onnormalizePath. For tool evidence, keep the original path bytes for open/read and put the sanitized string in a display field only (the fix: sanitize hidden terminal strings in web evidence #705 follow-up already states that split). A brandedCanonicalPathis worth it only at the open/read boundary, where astringdisplay field cannot be passed without an explicit conversion. - Pyramid: type at the file-open boundary. Provenance is already an API. A repo-wide "no
includes(basename)" lint will flag legitimate substring checks. - Effort: M.
- Risk: Medium. Branding every path in the protocol is a wide type change. Limit it to the function that opens a user-visible path. Do not brand internal joins.
6. Reject empty retrigger commits; do not treat a wider timeout as a fix
- Category: a required check goes green because the job was rerun, an empty commit retriggered CI, or a timeout was raised.
- Evidence: empty non-merge commits since 2026-10-01 whose subject matches retry/rerun/retrigger: 99c8621 on #707 (
ci: retry flaky validation matrix), 3be3d0b on #718 (chore: rerun CI for web rerun confirmation fix), 5bf446c on #857 (UI timeout; the message says rerunning failed jobs was not permitted). #829 says the three flakes passed when only the failed jobs were rerun. #855 is the timeout-widening attempt and is unmerged. Current tree has notest.retry, vitestretry:, or--retryoutside product "retry" copy. 3 of 80 merged PRs, plus Flaky CI tests: session-history E2E anchor, Windows git-info refresh, Windows setup-config lock timeout #829 and test: fix #829 flakes at the source and cut fixed test waits #855. - Constraint: CI fails a push whose commit has an empty
git diff-treeand a subject matchingretry|rerun|retrigger. The prototype flagged those three SHAs and did not flag708a44b(#871). It does not ban a commit that touches a comment in order to get a new SHA. There is no vitest retry to delete. - Pyramid: CI check for the empty-commit shape. The rest is prose: the test-value skill already forbids hiding a regression behind retries, wider tolerances, or a rerun. A two-number timeout type does not help (item 7).
- Effort: S.
- Risk: Low for the empty-commit check. It will not catch a one-line comment retrigger. Do not fail every empty merge commit; those are naturally empty and were the majority of empty
diff-treeresults since October.
7. Drag-then-click and shared SSE budgets: do not add a type
- Category: drag suppression used a 250ms wall clock, so a slow post-drop render let the trailing click through. Separately, one
timeoutMsis both the first-SSE deadline and the lifetime deadline, both measured from attempt start, so a 5ms test budget loses to a 12ms runner delay and reportsfirst SSE event. - Evidence: Drag: 20a7b98 and current
SessionSidebar.tsx(suppressDragClickis a flag cleared by the trailing click or a later pointerdown, not a timer). The regression insession-sidebar.spec.tswaits 300ms and still expects notoggleWorkspace. Main run 38736671833 failedsession-sidebar.spec.ts:265becausetoggleWorkspacewas called once. #861 cites the same flake on run 38850838416. SSE: e0d6bcd on #857.provider.tsstill sets both deadlines fromattemptStartedAt. Production first-event defaults are 60s and 300s, so this is a test-clock bug, not the Windows lock bug. The prototype showed{ firstEventMs: 5, idleMs: 5 }still passes a positive-number check, and arming the idle clock at the first event still returnsfirst-eventwhen the first read is already 12ms late. A frozen clock, then a jump of one budget, returnslifetime. That is whatwithSseClockalready does. - Constraint: Leave the drag flag and its 300ms regression. Leave
withSseClockas the way the lifetime test moves time. Do not add a two-field timeout type. Do not widen the harness timeout. - Pyramid: already landed (flag, regression, test clock). A type is the wrong layer; the prototype is the evidence.
- Effort: S, and the work is "do not undo this," not a new subsystem.
- Risk: Low. A future test can pass
timeoutMs: 5on a real clock and flake again. - Prose residue: that future test. The skill already tells an agent not to paper over it with a longer timeout. A lint for the literal
timeoutMs: 5is too narrow to keep.
What stays prose
Enforceable work is items 1–6. These are not enforceable, and the test-value skill already covers the last two:
- Whether a committed node should be read with
getByRoleor the paint helper. - A retrigger commit that is non-empty.
- A new watchdog written as a raw
setTimeoutbefore a spawn. - A new test that uses a tiny real
timeoutMsas if it were a phase selector.
No AGENTS.md addition is proposed. The skill sentence is enough for that residue, and this issue does not change the skill.
Activity