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
124 changes: 89 additions & 35 deletions .github/workflows/coil-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,54 @@ concurrency:
cancel-in-progress: true

jobs:
verify:
check:
name: Typecheck and lint
runs-on: ubuntu-latest
# The whole gate ran 13-17 minutes before the 2026-10-08 sync grew upstream's server suite
# from 354 to 524 test files (it took 28 minutes locally on an M-series Mac).
timeout-minutes: 40
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: true

# Run both even if typecheck fails, so a single run reports every failure.
- name: Typecheck
run: vp run typecheck

- name: Lint
if: ${{ !cancelled() }}
run: vp run lint

# The test jobs mirror upstream's ci.yml split: web and server each get their own runners, and
# everything else shares one. In a single job (before 2026-10-08) the web suite ran alongside
# mobile and desktop, and on these runners `MessagesTimeline.test.tsx` blew the 30s budget its
# own `beforeAll` pins for a heavy import (3 of 3 runs). An in-file literal beats
# `--hookTimeout`, so the only fork-side fix is to stop starving the web suite of CPU.
#
# `--testTimeout` is raised because `apps/web/vite.config.ts` pins testTimeout to 15s, which is
# tuned for upstream's blacksmith runners. On ubuntu-latest, upstream's `stashImageCompression`
# worst-case test (~3s on an M-series Mac; 6x the next slowest in its file) reliably exceeds 15s.
# Proven upstream-inherited, not a fork regression: branch `t3x/ci-control-upstream` — pristine
# upstream/main plus this workflow file and nothing else — fails identically (run 30386125253).
#
# 120000 is deliberate: it is the HIGHEST value any package configures
# (apps/server/vite.config.ts pins 120s for sqlite+git+orchestration integration tests). The
# flag applies to every package, so a lower value would silently CUT apps/server's budget. At
# 120s no package is lowered: web 15s -> 120s, root 60s -> 120s, server 120s -> 120s. Each job's
# `timeout-minutes` remains the backstop against a genuinely hung test.
#
# `--hookTimeout` is raised for the same reason and is NOT covered by `--testTimeout`: a
# `beforeAll`/`beforeEach` gets its own budget, unless the file passes one itself.
# Keep in sync with the `test` case in `scripts/coil/sync-upstream.sh`.
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
Expand All @@ -50,42 +93,53 @@ jobs:
- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

# Run all three even if an earlier one fails, so a single run reports every failure.
- name: Typecheck
run: vp run typecheck
# `@t3tools/monorepo` is the root package; its `test` script is `vp run -r test`.
- name: Test
run: vp run --filter '!t3' --filter '!@t3tools/monorepo' --filter '!@t3tools/web' test --testTimeout=120000 --hookTimeout=120000

- name: Lint
if: ${{ !cancelled() }}
run: vp run lint
test_web:
name: Test Web
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: true

- name: Test
run: vp run --filter @t3tools/web test --testTimeout=120000 --hookTimeout=120000

# apps/server sets `fileParallelism: false`, so its 524 test files run one at a time. Upstream
# shards them six ways by recorded duration (apps/server/src/testUtils/weightedShardSequencer.ts).
test_server:
name: Test Server ${{ matrix.shard }}
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6]
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: true

# Upstream's ServerBrowserPage.test.ts drives a real headless Chromium; upstream's ci.yml
# installs it the same way before its server tests.
- name: Install Chromium for server browser integration tests
run: node apps/server/node_modules/playwright-core/cli.js install --with-deps --only-shell chromium

# `--testTimeout` is raised because `apps/web/vite.config.ts` pins testTimeout to 15s, which is
# tuned for upstream's blacksmith runners. This fork runs on 2-core ubuntu-latest, where
# upstream's `stashImageCompression` worst-case test (~3s on an M-series Mac; 6x the next
# slowest in its file) reliably exceeds 15s and fails the whole gate.
#
# Proven upstream-inherited, not a fork regression: branch `t3x/ci-control-upstream` — pristine
# upstream/main plus this workflow file and nothing else — fails identically (run 30386125253).
#
# 120000 is deliberate: it is the HIGHEST value any package configures
# (apps/server/vite.config.ts pins 120s for sqlite+git+orchestration integration tests). The
# flag applies to every package, so a lower value would silently CUT apps/server's budget in
# half — the same failure class this override exists to prevent. At 120s no package is
# lowered: web 15s -> 120s, root 60s -> 120s, server 120s -> 120s.
# The job's own `timeout-minutes` remains the backstop against a genuinely hung test.
#
# `--hookTimeout` is raised for the same reason and is NOT covered by `--testTimeout`: a
# `beforeAll`/`beforeEach` gets its own budget. At the 2026-08-02 sync,
# `apps/web/src/components/chat/MessagesTimeline.test.tsx` (byte-identical to upstream, not a
# fork seam) timed out in a hook under full-suite parallel load while passing in 2.3s on its
# own — the same load-sensitivity class as the test timeout above, on hardware faster than the
# fork's 2-core runners. 120000 is chosen on the same "highest configured value" rule:
# web 15s -> 120s, root 60s -> 120s, server already 120s.
# Keep in sync with the `test` case in `scripts/coil/sync-upstream.sh`.
- name: Test
if: ${{ !cancelled() }}
run: vp run test --testTimeout=120000 --hookTimeout=120000
run: vp run --filter t3 test --shard ${{ matrix.shard }}/${{ strategy.job-total }} --testTimeout=120000 --hookTimeout=120000
20 changes: 13 additions & 7 deletions .github/workflows/coil-sync-resolve.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ jobs:
contains(github.event.issue.labels.*.name, 't3x-sync') ) &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) )
runs-on: ubuntu-latest
timeout-minutes: 45
# The verify below runs the whole suite on this one runner; the server suite alone takes about
# 25 minutes serially since the 2026-10-08 sync (coil-ci.yml shards it six ways).
timeout-minutes: 90
steps:
- name: Checkout fork main (full history)
uses: actions/checkout@v6
Expand Down Expand Up @@ -138,12 +140,16 @@ jobs:
changed the semantics of an API the fork hooks into. If upstream has absorbed a
fork change, delete the fork's now-redundant version as a normal follow-up commit
on this branch and say so — a merge never drops fork code for you.
4. Verify: run `vp run typecheck`, `vp run lint`, then
`vp run test --testTimeout=120000`. The --testTimeout flag is required: apps/web
pins 15s, which is too tight for this fork's 2-core runners, and 120s matches the
fork's CI gate without lowering any package's own budget. Note the flag must NOT
come after a `--` separator or it is silently ignored. Fix the fork's code until
all three pass.
4. Verify: run `vp run typecheck`, `vp run lint`, then the tests in two runs, web
last and alone:
`vp run --filter '!@t3tools/monorepo' --filter '!@t3tools/web' test --testTimeout=120000 --hookTimeout=120000`
then `vp run --filter @t3tools/web test --testTimeout=120000 --hookTimeout=120000`.
The timeout flags are required: apps/web pins 15s, which is too tight for this
runner, and 120s matches the fork's CI gate without lowering any package's own
budget. Web runs alone because a web test's in-file 30s hook budget times out
when it shares this runner with mobile and desktop. Note the flags must NOT come
after a `--` separator or they are silently ignored. Fix the fork's code until
all three steps pass.
5. Push the branch: `git push -u origin HEAD`.
6. Open a PR into `main` with `gh pr create`, title "coil: resolve upstream sync
(run ${{ github.run_id }})". In the body summarise: the upstream range merged,
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/coil-upstream-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ env:
jobs:
sync:
runs-on: ubuntu-latest
# Merge plus the same verify as coil-ci.yml, which needs up to 40 minutes on its own.
timeout-minutes: 50
# Merge plus coil-ci.yml's checks on this one runner; the server suite alone takes about 25
# minutes serially since the 2026-10-08 sync (coil-ci.yml shards it six ways).
timeout-minutes: 60
steps:
- name: Checkout fork main (full history)
uses: actions/checkout@v6
Expand Down
26 changes: 17 additions & 9 deletions docs/coil/sync-agent-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ The agent comments the PR link back on the issue when done.
> gh workflow run "coil sync resolve (agent)" -R radroid/t3code -f issue=<n> -f model=claude-opus-5
> ```

**Check the budget before you pick a path.** The job is capped at `timeout-minutes: 45` and
**Check the budget before you pick a path.** The job is capped at `timeout-minutes: 90` and
`--max-turns 150`. The one successful resolve to date absorbed a 36-commit upstream range against
37 fork commits and used 36m15s — 81% of the budget. A materially larger range will exhaust it, and
37 fork commits and used 36m15s, but that was before the 2026-10-08 sync, after which the test verify
alone takes about 30 minutes on one runner. A materially larger range will exhaust the budget, and
a timeout mid-merge leaves an unpushed branch and a spent budget. Raising the caps needs a workflow
edit; `workflow_dispatch` reads the workflow file from `--ref`, so you can carry raised limits on a
scratch branch without merging it (the job still checks out `main` and merges upstream into it):
Expand Down Expand Up @@ -113,8 +114,8 @@ upstream-inherited and nothing the fork did caused it. Delete the branch afterwa

Fix such failures in `.github/workflows/coil-ci.yml` (fork-owned) rather than by patching the upstream
file — patching adds a row to `docs/coil/SEAMS.md` and permanent sync cost for a CI-environment
problem. The `--testTimeout` override on the Test step exists for exactly this reason; its comment
records the case.
problem. The `--testTimeout`/`--hookTimeout` overrides and the separate web job exist for exactly
this reason; the comment above the test jobs records the cases.

## What the agent does (and what a human doing it locally should do)

Expand All @@ -140,13 +141,20 @@ This is the checklist the workflow prompt mirrors — follow it if you resolve l
really exists upstream, delete the fork's redundant copy as its own commit on the branch, and
note it in the PR. (The old rebase-era "dropped patch" escalation is gone; nothing can vanish
on its own any more.)
6. Verify: `vp run typecheck && vp run lint && vp run test --testTimeout=120000`. Fix the fork's
code to match upstream's new internals until green.
6. Verify: `vp run typecheck && vp run lint`, then the tests in two runs with web last and alone,
as `scripts/coil/sync-upstream.sh` does:

```
vp run --filter '!@t3tools/monorepo' --filter '!@t3tools/web' test --testTimeout=120000 --hookTimeout=120000
vp run --filter @t3tools/web test --testTimeout=120000 --hookTimeout=120000
```

Fix the fork's code to match upstream's new internals until green.

Two notes. **`AGENTS.md` says "do not run repo-wide checks" — an upstream sync is the sanctioned
exception.** A sync can break any package, so the full suite is the point; that rule is about
routine feature work. And the `--testTimeout` flag is required (see the section above); it must
not follow a `--` separator or it is silently ignored.
routine feature work. And the timeout flags are required (see the section above); they must
not follow a `--` separator or they are silently ignored.

7. **When green:** push the branch and open a PR into `main` (`gh pr create`). A human reviews
and **lands it with a merge commit — see
Expand Down Expand Up @@ -246,7 +254,7 @@ used to be the expensive one.
the exact pre-sync tip if you want to diff against it or reset to it; if its push failed, the
issue says `none`.
- To re-gate locally before landing:
`git switch --detach origin/coil/sync-<id> && vp run typecheck && vp run lint && vp run test`.
`git switch --detach origin/coil/sync-<id>`, then the verify in step 6.

## One-time setup

Expand Down
44 changes: 28 additions & 16 deletions scripts/coil/sync-upstream.sh
Original file line number Diff line number Diff line change
Expand Up @@ -189,25 +189,37 @@ if [[ "$SKIP_VERIFY" == 1 ]]; then
exit 0
fi

# Per-script extra args, so the daily verify matches the fork's CI gate exactly. `test` needs
# --testTimeout because apps/web pins 15s (tuned for upstream's blacksmith runners) and this fork
# runs on 2-core ubuntu-latest, where an upstream CPU-bound test reliably blows it. 120s is the
# highest any package configures (apps/server), so it never LOWERS a package's own budget.
# --hookTimeout is the same fix for the same reason and is NOT implied by --testTimeout: a
# beforeAll/beforeEach carries its own budget, and an upstream web test timed out in one under
# full-suite load at the 2026-08-02 sync while passing standalone.
# Each verify script is one `$RUN <script>`, except `test`, which runs the web suite on its own
# after everything else. coil-ci.yml gives web its own runner for the same reason: alongside
# mobile and desktop on one ubuntu-latest runner, `MessagesTimeline.test.tsx` blows the 30s its
# `beforeAll` pins in-file (3 of 3 runs at the 2026-10-08 sync), and `--hookTimeout` cannot
# override an in-file value. `@t3tools/monorepo` is the root package, whose `test` is
# `vp run -r test` and would re-run web.
# --testTimeout because apps/web pins 15s (tuned for upstream's blacksmith runners), too tight
# for an upstream CPU-bound test here. 120s is the highest any package configures
# (apps/server), so it never LOWERS a package's own budget. --hookTimeout is NOT implied by
# --testTimeout: a beforeAll/beforeEach carries its own budget.
# Keep in sync with `.github/workflows/coil-ci.yml`.
TEST_FLAGS="--testTimeout=120000 --hookTimeout=120000"
for script in $VERIFY; do
EXTRA=""
if [[ "$script" == "test" ]]; then EXTRA="--testTimeout=120000 --hookTimeout=120000"; fi
echo "→ verify: $RUN $script $EXTRA"
# shellcheck disable=SC2086
if ! $RUN "$script" $EXTRA; then
FAILING_STEP="$script"
fail "verification step '$script' failed"
write_status daily verify-failed "merge clean but '$script' failed"
exit 30
if [[ "$script" == "test" ]]; then
commands=(
"$RUN --filter !@t3tools/monorepo --filter !@t3tools/web test $TEST_FLAGS"
"$RUN --filter @t3tools/web test $TEST_FLAGS"
)
else
commands=("$RUN $script")
fi
for cmd in "${commands[@]}"; do
echo "→ verify: $cmd"
# shellcheck disable=SC2086
if ! $cmd; then
FAILING_STEP="$script"
fail "verification step '$script' failed"
write_status daily verify-failed "merge clean but '$script' failed"
exit 30
fi
done
done

write_status daily ok "merge clean and verification green"
Expand Down
Loading