Skip to content

fix(timeouts): prove request envelopes cover their worst-case work - #2916

Merged
thymikee merged 4 commits into
mainfrom
fix/2879-envelope-budgets
Sep 24, 2026
Merged

thymikee merged 4 commits into
mainfrom
fix/2879-envelope-budgets

Conversation

@thymikee

@thymikee thymikee commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Refs #2879 (Step 1). Makes the client request-envelope-covers-worst-case invariant executable
for fold and prepare, instead of living only in comments and hand-summed literals.

  • fold: the ledger test drives fold through the real public-client → daemon → fake
    Apple-tool-provider route, summing per-step timeoutMs from real recorded calls (not a
    hand-sum); every recorded call must carry a finite, positive timeoutMs or the fake throws.
    FOLD_REQUEST_TIMEOUT_MS moves 210 s → 255 s (post-fix(ios): drop -Werror from runtime clang builds and cache the fold helper build #2858 helper-build cost, matching the
    issue's prediction).
  • prepare: a handler-level test ties session-prepare.ts's fallback runner budget to the
    client envelope with the standard margin. PREPARE_REQUEST_TIMEOUT_MS is now derived from an
    unchanged PREPARE_STARTUP_BUDGET_MS (240 s) plus the 30 s margin (270 s), not a separate
    hand-set literal.
  • Step 2 (batch/replay, longpress, install/reinstall/install_source) is out of scope;
    those issues are not yet filed (read-only gh access this stage).

Touched files: 5 (timeout-policy.ts, session-prepare.ts, three test files).

Validation

Tested SHA: c5815897530bd4606ae0622799bc2e454d0092b6 (rebased onto main 0.21.14, 622435ecc5).

  • pnpm check:affected --run: 478/478 files, 3538/3538 tests, exit 0.

  • pnpm check:production-exports: exit 0; 68 pre-existing unused-export findings, none in this
    diff's files.

  • pnpm check:layering: 246/246 green (R11, R13 included).

  • #2879 mutation checks, each reverted after its run:

    mutation result
    none (baseline) passes; worst case 220,992 ms + 30,000 ms margin ≤ 255,000 ms envelope
    IOS_HINGE_ANGLE_TIMEOUT_MS 20s → 30s fails: worst case 260,992 ms exceeds envelope
    IOS_FOLD_POSE_SETTLE_ATTEMPTS 4 → 6 fails: worst case 260,990 ms exceeds envelope
    extra unrecorded 30s simctl call in fold pose fails: worst case 250,991 ms exceeds envelope
    fake doesn't record the clang build fails: ledger is missing the helper-build phase
    prepare fallback reverts to PREPARE_REQUEST_TIMEOUT_MS fails: 300,000 > 270,000

Live validation: not applicable. Both changes are client-side envelope-constant arithmetic; no
step's timeout, retry, or order in the real fold/prepare route changed, so there is no on-device
timing to observe beyond what the ledger and handler tests already assert.

Remaining risks: Step 2 issues still need filing before #2879 can close.

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.81 MB 4.81 MB +3 B
Package (unpacked) 4.81 MB 4.81 MB +3 B
Package (download) 1.44 MB 1.44 MB +9 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.1 ms 27.6 ms -0.4 ms
CLI --help 79.8 ms 78.0 ms -1.7 ms

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed at 8ff29c0. The ledger test drives fold through the real client, daemon, and fake provider route and sums the recorded step budgets, and the prepare test ties the envelope to the handler budget..

The only production change widens the fold (240 s to 255 s) and prepare envelopes, so it can only reduce client timeouts. Smoke Tests was still queued or running at review time, so there is nothing to attribute yet. I did not run the tests or the #2879 mutation checks myself.

Not blocking: it would help to record the #2879 mutation-check results in the Validation section, and to use "Refs #2879" instead of "Closes #2879", because the issue stays open until the Step 2 issues are filed.

The branch now conflicts with main, so it needs a rebase before the label can go on.

FOLD_REQUEST_TIMEOUT_MS was a literal whose basis lived only in a
comment, and that comment kept drifting from the route: it first cited
one 30s helper build and four 20s hinge reads, missing two 5s
display-inventory reads and the HID-dispatch guard/grace, then #2858
hand-summed the route again for its own preparation-phase change and
landed a fresh hand-summed figure with the same failure mode.

Add a ledger test that drives fold through the public client and
daemon against a fake Apple tool provider recording each call's
timeoutMs, including the fold-helper cache's toolchain probes and
build. A calibration run learns the route's hinge-settle-attempt count
from an oscillating angle that never settles; a measured run uses the
same oscillation but lands the last allowed read on target, so it
succeeds after making exactly as many reads. Summing every call's
timeout (minus 1ms, plus any kill grace) gives the route's worst case
that still succeeds, asserted against the resolved command envelope
with the usual 30s daemon-result margin. The test imports no
platform-apple step figure other than MAX_FOLD_DURATION_MS, so it
keeps proving the bound however the route's steps change next.

Widen FOLD_REQUEST_TIMEOUT_MS to 255_000 (the smallest 5s multiple at
least ledger + margin) and point both the constant's comment and the
descriptor-timeout-policy pinning test at the ledger test instead of a
hand-summed figure.
…udget

readPrepareIosRunnerTimeoutMs's fallback and the client envelope both
resolved to PREPARE_REQUEST_TIMEOUT_MS (240_000), so with no
--timeout the daemon-side runner budget and the client envelope were
identical: a slow cold runner build ended in a client-side timeout and
daemon reset instead of the daemon's own typed runner_phase_budget
result. Every other bounded command keeps a 30s margin between its
daemon-side budget and its envelope; prepare's explicit-timeout case
already widened past its own base by that same margin
(widenToUserBudget), only the default case had none.

Split the daemon-side runner budget out as PREPARE_STARTUP_BUDGET_MS
(240_000, unchanged) and derive PREPARE_REQUEST_TIMEOUT_MS from it
plus the margin (270_000). The handler falls back to the startup
budget, not the now-wider request timeout.

Add a handler-level test that calls handlePrepareCommand directly with
a fake runner binding recording the timeoutMs it receives, then checks
that value plus the margin against the same request's resolved client
envelope for the no-flag, --timeout above default, and --timeout below
default cases - the rule the fix proves, not just the constants it
compiles to.
The fold worst-case ledger fake defaulted an unset timeoutMs to 0, so a
provider call reaching it with no bound cost 0 virtual ms instead of
exposing the true unbounded worst case. Its runCommand handler also
skipped `open -a Simulator` before recording, dropping any such call
from the ledger with no error.

Build the ledger on the shared recording provider and assert every
call carries a finite, positive timeoutMs, so an unbounded call fails
the test instead of passing silently.
The ledger test runs two cold folds through a fresh daemon each. After a
platform-apple source change the first import is untransformed, and the
test ran past vitest's 5 s default, which hid the ledger printout behind a
timeout. Match the provider-scenario precedent of an explicit budget.
@thymikee
thymikee force-pushed the fix/2879-envelope-budgets branch from 8ff29c0 to 6c7f602 Compare September 24, 2026 16:57
@thymikee

Copy link
Copy Markdown
Member Author

Rebased onto main at 6c7f602, starting from the impl-2879 worktree's local rebase (c1355a6, idle for about 55 minutes and not pushed), so its "cold-import budget" commit is included. Main removed CHANGELOG.md (#2922), so the modify/delete conflicts are resolved by dropping the file. The provider tests that timed out in the gate under host load around 500 pass alone on this head (13/13). The body now says Refs #2879 per the 15:29Z review. Still open from that review: the #2879 mutation-check results for the Validation section. Those are the owner's to add.

@thymikee
thymikee force-pushed the fix/2879-envelope-budgets branch from 6c7f602 to c581589 Compare September 24, 2026 17:32
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the review at 8ff29c0. New head c581589.

Rebase (main removed CHANGELOG.md in #2922): main advanced twice more since the last push
(dc9ab863ad → 2c7856d560 → 622435ecc5, 0.21.14). Rebased again onto 622435ecc5. The
modify/delete conflicts on the two oldest commits were resolved by keeping the file deleted
(git rm CHANGELOG.md); no changelog.d/ fragment was added, no commit message mentions the
changelog. The only diff from the previously reviewed commits is the dropped CHANGELOG hunks —
git range-diff shows commits 1–2 as ! (CHANGELOG-only), commit 3 as =.

#2879 mutation-check results (each reverted after its run):

mutation result
none (baseline) passes; worst case 220,992 ms + 30,000 ms margin ≤ 255,000 ms envelope
IOS_HINGE_ANGLE_TIMEOUT_MS 20s → 30s fails: worst case 260,992 ms exceeds envelope
IOS_FOLD_POSE_SETTLE_ATTEMPTS 4 → 6 fails: worst case 260,990 ms exceeds envelope
extra unrecorded 30s simctl call in fold pose fails: worst case 250,991 ms exceeds envelope
fake doesn't record the clang build fails: ledger is missing the helper-build phase
prepare fallback reverts to PREPARE_REQUEST_TIMEOUT_MS fails: 300,000 > 270,000

Each failure prints the ledger. Kept the settle-attempts mutation's test on a 20s budget (added
last pass) — without it, that mutation times out at 5s instead of printing the ledger.

Wording: already Refs #2879 per the 15:29Z review; PR body's Validation section and Touched
files count (now 5) updated to match this head.

Gate on c581589: pnpm check:affected --run — 478/478 files, 3538/3538 tests, exit 0, one
slow-test note outside the delta (within variance). pnpm typecheck / pnpm lint / pnpm check:layering
(246/246) all exit 0. Targeted vitest (ios-fold.test.ts, session-prepare.test.ts,
command-descriptor-timeout-policy.test.ts): 16/16, stable across 5 runs through the rebases.

Evidence (targeted vitest, this head):

npx vitest run --maxWorkers=2 --project provider-integration test/integration/provider-scenarios/ios-fold.test.ts
Test Files  1 passed (1)   Tests  2 passed (2)

npx vitest run --maxWorkers=2 --project unit-core src/daemon/handlers/__tests__/session-prepare.test.ts src/__tests__/command-descriptor-timeout-policy.test.ts
Test Files  2 passed (2)   Tests  14 passed (14)

Envelope-mutation control (FOLD_REQUEST_TIMEOUT_MS 255s → 240s, reverted after):

AssertionError: fold ledger worst case (220992ms) + 30000ms margin exceeds the 240000ms envelope.

gh pr view 2916 --json mergeable → MERGEABLE after the push.

@thymikee

Copy link
Copy Markdown
Member Author

The code looks correct at 6c7f602, and this round fixes the conflict from the earlier review at 8ff29c0.

The Validation section still names the tested SHA as 8ff29c0, not 6c7f602; the logical patch only differs by the 20 s test budget, but can you update it to point at the reviewed commit?

Can you record the #2879 mutation-check results against 6c7f602 in the Validation section: the three fold-ledger mutations (hinge timeout at 30 s, settle attempts at 6, one extra 30 s runXcrun call) and the session-prepare fallback revert? I read the code and believe the test goes red on the old 240_000 ms envelope, but I did not run the ledger test myself, so I can't confirm the measured virtualElapsedMs or that it exceeds the 210 s threshold that would fail the old envelope.

The two Smoke Tests jobs are still running and I have no failure excerpt from them. This PR widens the client envelope for prepare (240 s to 270 s) and fold (240 s to 255 s), and iOS smoke may run prepare ios-runner; since the change only widens those envelopes, it should not make a request time out sooner. The rest of the diff (runner Swift, ADRs, Android hierarchy) comes from the rebase onto upstream, not from this change.

The next step is to add the #2879 mutation-check results to the Validation section against 6c7f602 and wait for Smoke Tests to finish.

The branch has since been rebased to c581589; its four commits are identical to the ones reviewed here, so this review covers that head too.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 24, 2026
@thymikee
thymikee merged commit 438b43c into main Sep 24, 2026
18 checks passed
@thymikee
thymikee deleted the fix/2879-envelope-budgets branch September 24, 2026 17:41
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-24 17:42 UTC

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

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant