Repository navigation
[Bug]: four tests exceed their timeouts in the unsharded macOS control lane, first observed once the lane was able to finish #4997
Description
Client or integration
CI only; not a client-facing path.
Area
CI and release
Summary
Four tests exceed their own timeouts when the full suite runs unsharded in the macos control lane. They are not new failures. They have never been reachable, because that lane has a 30-minute job budget and the suite needs about fifty minutes, so every run has been cancelled slightly past the halfway point for months and these four sit beyond it.
An authorized one-off measurement raised only the control job budget to 60 minutes on a throwaway branch and let the lane finish for the first time. Test step 23:13:17Z to 00:03:56Z, 50m39s wall, Bun reporting 3034.18s. Result: 26475 pass, 46 skip, 5 fail across 26526 tests in 1343 files. The suite exited 1 on its own rather than being killed, so this is a complete run.
One of the five failures is the measurement itself and is expected: tests/ci-workflows/ci-workflows.test.ts:121 pins the control budget at 30 and the branch set it to 60. That guard is working. The other four are the subject of this issue:
| Test | Observed | Bound |
|---|---|---|
| web-search heartbeat retry | 6233.05ms | 5000ms |
| image non-droppable | 60323.51ms | 60000ms |
| imageTierBias | 91516.75ms | 60000ms |
| provider-option ownership spine | 31801.03ms | 30000ms |
Three are marginal and one overshoots by half again, which is usually the spread that separates a budget sized on a quiet machine from a genuine stall. They need individual dispositions and none should be resolved by raising a bound before someone has explained where the time goes. tests/helpers/test-budget.ts states the two conditions a budget increase has to meet, and they apply here.
Worth noting why this lane is worth keeping rather than sharding away: it is the only place the suite runs in one process, so it is the only oracle for shared-process and cross-file ordering pressure. These four results are the first thing it has ever told us.
The 30-minute budget itself is #4905. This issue is the four tests, not the budget.
Reproduction
Not reproducible locally and no local verification was attempted. It requires the unsharded control lane to run to completion, which currently requires a job budget larger than the one in ci.yml.
Version
2.59.0 (dev), measured against base 61ee64747bedc5fafbeb5fc811898ea4db4ec738.
Operating system
macOS, GitHub-hosted runner.
Provider and model
Not provider-specific.
Logs or error output
26475 pass
46 skip
5 fail
383039 expect() calls
Ran 26526 tests across 1343 files. [3034.18s]
Screenshots and supporting files
Not applicable.
Redacted configuration
Not applicable.
Activity
lidge-jun commentedon Sep 18, 2026
리뷰 · 우선순위 64 / 80
이 이슈는 unsharded
macos control레인이 처음으로 끝까지 갔을 때 드러난, 테스트 자체 timeout 네 건입니다. tip(e80e571f63, package 2.59.0)의.github/workflows/ci.ymlmacos-control은 jobtimeout-minutes: 30(#4905)이고, Test 스텝은bun test --isolate --timeout 60000 tests입니다. 레인 기본 per-test 천장 60초와, 테스트가 직접 적은 더 짧은 bound가 섞여 있습니다. 본문의 60분 측정은 예산만 잠깐 올린 one-off이고, 이 이슈의 대상은 job 예산이 아니라 그 네 테스트입니다.tip에서 이름을 대조하면 대략 이렇게 대응됩니다. (1)
tests/web-search/web-search.test.ts의 “retry wait longer than the stall budget still succeeds (heartbeats feed the watchdog)” — 명시5_000, 관측 6233ms. (2)tests/adapters/anthropic/anthropic-image-normalize.test.tsN1 “oversized-dimension PNG … not dropped” — 파일에 per-test timeout 없음 → 레인 60s, 관측 60323ms. (3)tests/adapters/anthropic/anthropic-image-retry.test.tsimageTierBias 플럼빙 — 역시 명시 timeout 없음 → 60s, 관측 91516ms로 bound를 크게 초과. (4)tests/adapters/openai/openai-provider-option-e2e.test.ts“keeps Pool, Direct, and API ownership stable…” — 명시30_000, 관측 31801ms. 세 건은 경계 근처, imageTierBias 한 건은 절반 이상 넘깁니다.측정 숫자도 맥락이 있습니다. 스위트 wall 약 50m39s, Bun 3034s, 26475 pass / 5 fail 중 하나는 ci-workflows가 control budget 30을 핀하는 가드가 60으로 바뀐 측정 자체입니다. 가드가 일을 한 것이고, 남은 네 건만 disposition 대상입니다. 로컬 재현은 본문대로 불가에 가깝고, 계측 PR도 호스티드 macos-control(또는 동등 unsharded)에서 돌려야 의미가 있습니다.
tests/helpers/test-budget.ts정책은 예산을 올리려면 (1) 대기가 주장 자체에 Intrinsic하고 (2) 동작을 제거해도 테스트가 여전히 실패해야 한다고 못 박습니다. 네 건 모두에 맹목timeout/--timeout상향은 금지에 가깝습니다. 특히 N1·imageTierBias는 실제 Bun.Image 리사이즈/인코드가 있어 경합 민감하지만, 어디에 시간이 갔는지(인코드 호출 수, 캐시, 동시성, 픽스처 크기) 설명이 없습니다. ownership spine은 프로세스·ACL·관리 API를 한 덩어리로 도는 통합이라 30s가 타이트할 수는 있으나, 역시 ablation·구간 계측 없이 숫자만 키우면 안 됩니다.레인을 유지할 이유는 본문과 같습니다. 단일 프로세스 전체 스위트 오라클이라 shared-process·교차 파일 순서 압력을 샤드가 못 봅니다. #4905 job 30분과 이 네 건은 분리해서 추적하는 편이 맞습니다. types/config 스플릿과 무관합니다.
점수는 중상입니다. 신호는 진짜이고 재현 조건이 문서화돼 있지만, 운영자 대면 버그가 아니라 CI 신뢰성·예산 규율 이슈입니다. 다음 액션은 per-test disposition 네 장입니다.
tests/web-search/web-search.test.ts:1218 - heartbeat retry, bound 5s. 1.5s backoff+stall 1s 설계상 여유 부족 가능. 구간 타이밍 후 소폭 상향 또는 인위 대기 축소.
tests/adapters/anthropic/anthropic-image-normalize.test.ts (N1 not dropped) - 레인 60s에 60.3s. Bun.Image 경로 계측 없이 budget 올리지 말 것.
tests/adapters/anthropic/anthropic-image-retry.test.ts (imageTierBias) - 60s에 91s. 가장 수상. 인코드/리사이즈 hang·중복 작업 여부 먼저.
tests/adapters/openai/openai-provider-option-e2e.test.ts:123 - ownership spine 30s에 31.8s. 단계별 elapsed 로그 후 SERVER/SPAWN 예산 정책과 照合.
tests/helpers/test-budget.ts - 예산 상향 두 조건. 이 이슈의 기본 심판 기준.
메인테이너의 판단이 필요한 지점
너의 추천
job timeout을 올리는 PR은 받지 마세요. 네 테스트마다 (관측 구간 → tip 코드에서 시간 sink → ablation → 그다음에만 budget) 순서로 이슈를 쪼개거나 체크리스트로 닫으세요. imageTierBias부터 보는 게 좋습니다. 라벨은 바꾸지 마세요.
이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 18, 2026
First completed
macos controlrun. Until now this lane had only ever produced cancellations, so there was no measurement to argue from; #5037 was why, and #5048 fixed it.Run 35318628931, job 105515988406, frozen ref
ci/control-probe-2590at56a99d3848. Started 07:52:52Z, completed 09:03:27Z.The suite itself took 69m45s, inside the 75-minute budget #5028 gave it, so the budget is right and this issue is the whole remaining gap. The four failures are the four named here, all timeouts, none of them assertions:
sideband GET /v1/live/{callId} relays the exact frame ceiling bidirectionallyshellStreamExec completion acknowledgement > appends structured shellResult and streamClose after the exit eventopenai-chat inline image normalization > imageTierBias from incoming meta reaches the normalizerOpenAI provider-option integration spine > keeps Pool, Direct, and API ownership stable across transports and managementTwo things this measurement settles.
It is the same four every time.
imageTierBiaswas recorded at 91516ms when this issue was opened and came in at 83040ms here, so the overrun is large and repeatable rather than marginal — except for the provider-option spine, which missed by 29ms and is a genuine edge case.And all four pass in
macos 1/2andmacos 2/2, which run the same files. The difference is that the control lane loads all 1367 files into one process. Whatever is being measured here is contention in that process, not the behaviour under test, which is why raising these four budgets would be the wrong fix: it would buy green by agreeing to stop noticing.The lane can now be dispatched and will report.
gh workflow run ci.yml --ref dev -f lane=macos-controlis enough; the frozen ref is no longer needed.lidge-jun commentedon Sep 18, 2026
Third control dispatch: the lane ran out of its 75-minute budget, and the failing set was a different five.
Run
35343636646(job105595102031) was dispatched atc5b39d43e6. The Test step started 12:15:49Z and the job was cancelled at 13:31:19Z bytimeout-minutes: 75, insidetests/providers/kiro/kiro-usage-quota.test.ts. There is no final tally for this run: it was cut off, not finished.What it did report before the cut is the part worth recording. Five failures, and not one of them is in the table above, nor in the set from the previous completed control (run
35318628931at56a99d3848: 26822 pass / 4 fail / 56 skip, 26882 tests across 1367 files, 4184.63s).Codex autostart shim > Unix timeout cleanup preserves EPERM when passive probes keep failingcodex-auth API > pool plan persistence fails closed without waiting behind a busy config writercodex-auth-api.test.ts:2379expected < 2000, received 5350.45Codex reset-credit recovery coordinator > expires an abort-ignoring revalidation without dispatchconnection-reset recovery parity on the web-search legs > the sidecar leg replays a reset on a fresh connection while keeping the provider HTTP version pin[upstream-retry] connection reset (web-search-loop) — retrying (2/3)before failingsocks5Fetch > closes a keep-alive socket after a fixed-length response completesexpect(...)true/false attests/lib/socks5-fetch.test.ts:171The last one is different in kind from everything else this lane has produced. It failed in 294ms against a plain boolean expectation, so it is not a budget overrun at all: it observed the socket before the close had been applied. That race can misfire in a sharded lane too, which the other entries cannot, and it deserves a look on its own terms rather than as another line in a timing table.
Three dispatches have now reached or nearly reached the end of the suite, and each produced a disjoint failing set. The common factor is sensitivity to elapsed time in a single process holding 1367 files, not any one subsystem. That is the argument against fixing any of these by raising a bound: a larger number moves the boundary to a different set of tests without removing the class. The four tests this issue is about passed again in the sharded lanes at the current dev tip
f02f3613be, where the aggregate isci: success.A separate fact about the lane itself, recorded here because it changes how much this evidence is worth rather than what it means. 4184.63s is 69.7 minutes of test execution alone; adding checkout, dependency install, and the GUI build puts the job at its 75-minute bound, which is exactly where it stopped. I am not proposing a bump. Widening it buys one more run of the same shape and pushes back the point at which a genuine hang becomes visible, which is the reason the comment in
ci.ymlgives for choosing 75 in the first place. If this lane is to stay usable as release evidence, the thing to change is the tests that assert on wall-clock duration, so the result stops depending on how loaded the machine is.Dev tip state at the time of writing, for whoever picks this up:
f02f3613beis green on every sharded leg, GUI locale parity is clean across all nine locales againsten.ts(2818 keys), and the file-size ratchet has no offenders with 37 of 51 entries sitting exactly at their cap.lidge-jun commentedon Sep 18, 2026
The sideband test in this table is not confined to the unsharded control lane. It failed in the ordinary sharded
macos 2/2leg ondevatecd3adae75, run35363697700, at 15318.60ms against its own 15000ms bound:That matters for how this issue is read. The framing so far has been that the unsharded pool is a special pressure environment and these four are what it surfaces. This occurrence had none of that: one of four macOS shards, the same budget, 318 milliseconds over. The bound is simply close enough to the work that ordinary runner variance crosses it.
It also means the disposition cannot be "these pass in the sharded lanes, so only the control lane needs attention". A test that overshoots by two percent will fail somewhere on a schedule nobody controls, and each occurrence costs a rerun and an investigation that reaches this same conclusion.
Recorded, not fixed here, because the 2.59.0 candidate was already frozen at
ecd3adae75when this fired and the fix belongs to the test rather than to the release.test(ci): make the #4997 phase record say something a reader can act on
test(ci): move fixture and host-probe cost out of the #4997 measured …
test(ci): split phase timing for the four #4997 budget overruns (#5081)
test(ci): move fixture and host-probe cost out of the #4997 measured …
lidge-jun commentedon Sep 19, 2026
A fourth control dispatch, and a fourth disjoint failing set. Run
35386429688oncodex/4997-phase-timing-evidencereported:Neither is in the table above, and neither was in the sets from the three earlier dispatches. Four runs have now produced four failing sets with no overlap between them, which is about as clear as this kind of evidence gets: the failing test is selected by load, not by subsystem.
The Cursor one is worth naming separately because it is a new shape. It is not a test budget being exceeded — it is production code declaring a stall.
src/adapters/cursor/live-transport.tsfails the turn when no inbound frame arrives for five seconds, and under control-lane contention the fixture's own frames do not arrive inside that window. A five-second inbound-frame expectation is a reasonable production contract and an unreasonable thing to assert against a synthetic server sharing a process with 1367 test files.The instrumentation that answers "which phase grew" landed on
devas7817cd57f7(#5081). It splits preparation, the measured execution and teardown-plus-reap, and an open phase emits progress ticks reporting whether the case's own counter moved, so a truncated record ending inmoving=yesis slow execution and one ending inmoving=nois a stall. Those were indistinguishable before: both looked like a log that stops mid-phase.The disposition PR is #5082, now rebased onto
dev. It moves confirmed preparation cost out of the measured window for the image and provider-option cases and does not touch any budget.test(ci): move fixture and host-probe cost out of the #4997 measured …
lidge-jun commentedon Sep 19, 2026
Disposition for the fifth failure on this lane, which is a different class from the four in the table above.
tests/providers/cursor/cursor-stream-health.test.tsfails withCursor stream stalled: no inbound frames for 5s before turnEnded, raised fromsrc/adapters/cursor/live-transport.ts. It is not a test exceeding its own budget. It is production code declaring a stall: the T04 watchdog fails a turn whose inbound frames stop arriving, and under control-lane contention the fixture's own frames do not arrive inside the window.Three things are worth separating here.
The five seconds is not the production contract.
CURSOR_STREAM_SILENCE_FAIL_MSis 30s. The case injectsisolationBudgetMs(1_000), whichtests/helpers/ci-watchdog.tsfloors at 5s on a non-Windows CI runner, and the stall message prints that injected budget back. So the number in the failure is the test's own scaled knob.The scaling is what makes this worse rather than better. The case asserts that no deadline expires while meaningful frames keep arriving, and it demonstrates that by observing progress for three times the silence budget. At a 5s floor that is a demand for 15 consecutive seconds during which no 5s gap in frame decoding occurs, inside a 22s fixture window. Raising the floor lengthens the exposure. The budget was already scaled once for this same case in #3940; there is no value of it that makes a "no deadline expired" assertion safe on a runner that can pause for more than ten seconds.
So this one should not be resolved by a budget at all. #5131 adds a T04-only clock seam —
now,setTimeout,clearTimeoutonCursorTransportFactoryInput, defaulting to the globals, in the shapeStageLeaseClockalready uses insrc/cli/account-api.ts— and rewrites the case to advance virtual time by hand between frames whose arrival it awaits. Contention can then delay a frame without any deadline passing. Production budgets, the first-frame timer, the turn-ended grace and the outbound heartbeat are untouched, and the firing half of the contract stays on real timers in three cases that assert a stall does happen, which a slow runner cannot turn red for being slow.One limitation worth recording against this issue: the lane that produced this failure still cannot finish inside its 30-minute budget (#4905), so hosted CI on that PR cannot demonstrate the failure disappearing. The argument is structural rather than empirical — the assertion no longer contains a term for elapsed real time.
No local suite was run for any of this; the analysis is static plus the run log quoted in this issue.
lidge-jun commentedon Sep 19, 2026
Disposition for the Cursor case recorded on this issue earlier: fixed on
devby #5131.It was never a test exceeding its own budget.
src/adapters/cursor/live-transport.tsfails the turn when no inbound frame arrives insideCURSOR_STREAM_SILENCE_FAIL_MS, and under control-lane contention the fixture's own frames did not arrive inside the window the test had configured. A five-second inbound-frame expectation is a defensible production contract and an indefensible thing to assert against a synthetic in-process server sharing a runtime with 1367 test files.The production timeouts are unchanged — 30s silence, 90s heartbeat-only. What changed is that the T04 watchdog now reads time and schedules its one timer through an injectable clock that defaults to the globals, so the test advances a virtual one-second silence by hand instead of waiting for a real one. Nothing else about the transport's timers moves with it, and the
elapsedMsdiagnostic deliberately stays on the wall clock becauseturnStartedAtis stamped there and the pair has to subtract coherently.That is the same shape as #5082, which replaced a host service-manager probe with a seam rather than relaxing anything. Four of these cases are now dispositioned by removing a dependency on the machine keeping up, not by giving the machine more time.
fix(ci): bundle L6 — re-attestation timestamps, event-driven sideband…
test(server): stop the sideband ceiling proxy before afterEach remove…
lidge-jun commentedon Sep 26, 2026
Closing: each of the four tests now has a specific fix on
dev, none of them a blind timeout raise. The sideband frame ceiling test got an event-driven wait and a stop-before-cleanup fix (#5740 e535c65, #5747 000abfc); the shellStreamExec completion-ack case was reshaped so the real foreground spawn no longer sits in its measured window (#5839 929d4ff); imageTierBias and the provider-option ownership spine carry phase timing with fixture and host-probe cost moved out of the measured windows (#5081 7817cd5, #5082 d385a56). #5028 sized the control job budget to the measured duration.