Skip to content

Measure and trim the Next.js build-trace step in the webapp build #568

Open

Description

@HMarzban

Part of #554. Change set E. Work on main. Rulings: #554 rulings 1, 4, 5, 15, 16.

Summary

The longest webapp next build phase is "Collecting build traces". It takes 99.3–118.6 s (median 107.4 s) in the prod Docker build and 39.0–67.6 s (median 54.7 s) in 🏗️ Build Verification. Nobody knows which work fills it, and Next 15.5.27 has no switch to turn it off.

This issue measures first: a trace artifact from Build Verification and two timing lines in the Docker log. Only if the go rule passes does next.config.js get outputFileTracingExcludes for build-only packages. An excluded runtime file breaks the image, so Step 8 proves each exclude locally first.

Measured windows ("Collecting build traces ..." to "Route (pages)"):

Run Deploy job (Docker) Docker window s next build s Build Verification job (hosted) Hosted window s
39398067267 118549257854 99.7 226.6 118548278109 39.0
39297468969 118217497479 99.3 185.4 118215792135 67.6
39275929831 118142985559 110.3 248.6 118141679143 42.2
38392614215 115281929767 118.6 262.5 115280995457 42.6
38239392350 114780192678 111.7 255.6 114778980229 66.8
38068638114 114262648443 104.4 209.1 114261976214 66.7

In job 118217497479 the other phases were shorter: type check 30.1 s, compile 36.7 s, page data and static pages 11.4 s.

Depends on / Blocks

Depends on nothing; blocks nothing. Same files, no order: #560 (apps/webapp/next.config.js lines 77-94, the Dockerfile builder stage), #565 (moves 🏗️ Build Docker Images into an images job), #557 (the comment at Dockerfile.bun:214-218).

Current behaviour

Links are pinned to d0a53756f8676556358b6adc6c2397d2ed7c64c8. Upstream Next links are pinned to tag v15.5.27.

  1. Standalone, traced from the monorepo root (next.config.js:70-75): output: 'standalone', outputFileTracingRoot: path.join(__dirname, '../../'), no outputFileTracingExcludes.

  2. A custom webpack function (next.config.js:135-167) turns the webpack build worker off (index.ts:1613-1618), so tracing runs in the main process after the compile.

  3. collectBuildTraces runs on every non-generate build (index.ts:2610-2611).

  4. The window covers two jobs. The spinner starts at index.ts:3826 and waits for the trace at index.ts:3972. It then copies every traced file into .next/standalone (index.ts:4014-4028), the write-standalone-directory span (index.ts:699). The route table prints after both (index.ts:4040). So the window is the tail of node-file-trace-build plus the whole copy.

  5. Span names are node-file-trace-build (collect-build-traces.ts:111) and apply-include-excludes (:611). With DEBUG=next:build:build-traces, Next prints finished build tracing <ms>ms (:737).

  6. How an exclude saves time. A key that matches next-server feeds the trace-time ignore list (collect-build-traces.ts:180-187, used at :342-345). An ignored file is never read, followed or copied. The key '*' matches next-server; a one-route key acts after tracing and saves copy time only. The matcher uses contains: true on paths relative to the monorepo root (:190-203), so **/node_modules/<package>/** matches Bun store paths, for example node_modules/.bun/rollup@4.63.1/node_modules/rollup/dist/rollup.js.

  7. Build-time packages through Sentry. @sentry/nextjs 10.76.2 depends on rollup, @rollup/plugin-commonjs, @sentry/bundler-plugins and @sentry/webpack-plugin (bun.lock:1465). @sentry/cli ships one native binary per platform (bun.lock:1441). Step 5 measures whether the trace reaches them.

  8. Build Verification keeps nothing (prod.docs.plus.yml:390-396):

    - name: 🏗️ Build Webapp
      run: bun run --filter @docs.plus/webapp build:ci

    The runner throws away apps/webapp/.next/trace. The repo pins actions/upload-artifact at 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 (prod.docs.plus.yml:250-258).

  9. The Docker build prints no timing (Dockerfile.bun:212-219):

    RUN --mount=type=cache,target=/app/apps/webapp/.next/cache bunx next build

    The layer is never reused (the git hash is inlined), so changing this line costs no cache hit.

  10. The image ships only the traced output (Dockerfile.bun:242-252): .next/standalone, .next/static and public.

  11. Missing-file checks today. The image HEALTHCHECK fetches /api/health (Dockerfile.bun:280-282). 🩺 Verify Deployment runs smoke webapp 3000 /api/health (prod.docs.plus.yml:766); a failure triggers 🚨 Rollback on Failure. Neither check loads a pad; Step 8 and Accountability cover that by hand.

  12. /c/[channelId], a Cypress harness page, returns 404 without NEXT_PUBLIC_E2E=true ([channelId].tsx:80-87) but still loads the chatroom module graph, the largest page bundle (214 kB in job 118217497479). A correct image answers 404; a broken one answers 500.

Target behaviour

  • After Steps 1-3, kept after close: every Build Verification run uploads webapp-next-trace-<sha> (trace and every *.nft.json from apps/webapp/.next, 7 days), and the upload never fails the job. Every webapp Docker build prints starting build traces and finished build tracing <ms>ms; the image and cache do not change. A "Measured, " comment splits the window into trace and copy time.
  • Only if the Step 6 go rule passes, after Step 7: one '*' key with candidate globs. .next/standalone gains no file and loses only excluded packages and their dependencies. The image starts healthy, a pad loads, and /c/<id> answers 404, not 500. The Docker window falls by at least 20 s, or Step 7 is reverted.

Implementation steps

Read every file in Read first before Step 0. "Comment here" means a comment on this issue, never a code comment.

Step 0. Read and check the base

Run on current main. Compare with Current behaviour.

git show main:apps/webapp/next.config.js | sed -n '70,75p;135,137p'
git show main:apps/webapp/docker/Dockerfile.bun | sed -n '212,219p;242,252p;280,282p'
git show main:.github/workflows/prod.docs.plus.yml | sed -n '250,258p;390,399p;766p'
git show main:apps/webapp/package.json | grep -n '"next":'
grep -n 'outputFileTracingExcludes' apps/webapp/next.config.js
grep -c -E "next:build:build-traces|finished build tracing|'node-file-trace-build'" apps/webapp/node_modules/next/dist/build/collect-build-traces.js
grep -c "'write-standalone-directory'" apps/webapp/node_modules/next/dist/build/index.js

Step 1. Upload the trace files from Build Verification

File: .github/workflows/prod.docs.plus.yml, job build. Add this right after 🏗️ Build Webapp (line 396), before 🏗️ Build Admin Dashboard:

      # Measurement aid for the "Collecting build traces" phase; it must never block a deploy.
      - name: 📤 Upload webapp build trace
        if: ${{ !cancelled() }}
        continue-on-error: true
        timeout-minutes: 5
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: webapp-next-trace-${{ github.sha }}
          path: |
            apps/webapp/.next/trace
            apps/webapp/.next/*.nft.json
            apps/webapp/.next/server/**/*.nft.json
          if-no-files-found: warn
          include-hidden-files: true
          retention-days: 7
          overwrite: true
  • include-hidden-files: true is required: the action merges the paths into the root apps/webapp/.next and skips dot paths by default, so it would upload zero files silently.
  • Copy the SHA pin from line 252. Never use a floating tag. overwrite: true lets "Re-run jobs" reuse the name.
actionlint -config-file .github/actionlint.yaml .github/workflows/prod.docs.plus.yml

Step 2. Print Next's trace timing inside the Docker build

File: apps/webapp/docker/Dockerfile.bun, the RUN ... bunx next build line (line 219 at the pinned commit). Replace that one line with:

# DEBUG prints "starting build traces" and "finished build tracing <ms>ms", so the
# deploy log splits the "Collecting build traces" phase into trace and copy time.
RUN --mount=type=cache,target=/app/apps/webapp/.next/cache \
  DEBUG=next:build:build-traces bunx next build

Keep the comment above the RUN line as on main (#557 owns it). Add no ARG or ENV, so no install cache key changes.

docker build --check -f apps/webapp/docker/Dockerfile.bun .

Then check that the lines print, under make dev-backend, not make dev-local (Step 8 says why). .env.local sets NODE_ENV=development and dotenv does not override it, so every local build here sets NODE_ENV=production.

bun scripts/check-next-server.ts
(cd apps/webapp && NODE_ENV=production DEBUG=next:build:build-traces bunx dotenv -e ../../.env.local -- bunx next build 2>&1 | grep 'build-traces')
rm -rf apps/webapp/.next

check-next-server.ts must exit 0. Expect starting build traces and finished build tracing <n>ms; ignore the laptop number. Then stop make dev-backend and start make dev-local again.

Step 3. Record the artifact in the workflow reference

File: .github/WORKFLOWS.md, §Active workflows, the workflows/prod.docs.plus.yml entry. Add one bullet after "Stages:":

  - Build Verification uploads the webapp Next trace files as the `webapp-next-trace-<sha>` artifact, kept 7 days. The webapp Docker build prints Next's `next:build:build-traces` debug lines. Both are measurement aids and never fail a run

Step 4. Hand-over checkpoint for the measurement change

git diff -- .github/workflows/prod.docs.plus.yml apps/webapp/docker/Dockerfile.bun .github/WORKFLOWS.md

Show the diff and stop for review. Then commit and push per SP2 (#554), as its own commit. The measurement rides normal deploys (ruling 4).

A webapp deploy is a push whose tip subject is a valid (build): subject with a front or back token and no no-deploy token. Only these set app_deploy=true in .github/scripts/parse-build-trigger.sh, so the deploy job runs (prod.docs.plus.yml:431-435) and rebuilds the webapp image.

Step 5. Read the spans and post the baseline split

You need one push run with Build Verification and two webapp deploys, all after Step 4.

  1. Hosted spans. Print the 25 longest spans:

    RUN=<run id>
    gh run download "$RUN" --repo docs-plus/docs.plus --pattern 'webapp-next-trace-*' --dir /tmp/next-trace
    ls -R /tmp/next-trace | head
    bun -e '
    const ev = (await Bun.file(process.argv.at(-1)).text()).trim().split("\n").flatMap((l) => JSON.parse(l));
    const byId = new Map(ev.map((e) => [e.id, e]));
    for (const e of [...ev].sort((a, b) => b.duration - a.duration).slice(0, 25))
      console.log((e.duration / 1e6).toFixed(1).padStart(7), "s", e.name.padEnd(36), "parent:", byId.get(e.parentId)?.name ?? "-");
    ' /tmp/next-trace/webapp-next-trace-*/trace

    duration is in microseconds. Record node-file-trace-build, apply-include-excludes and write-standalone-directory.

  2. Traced files per package, each file counted once by its innermost node_modules/<package>:

    bun -e '
    const path = require("node:path");
    const dir = path.resolve(process.argv.at(-1));
    const files = new Set();
    for await (const f of new Bun.Glob("**/*.nft.json").scan(dir)) {
      const nft = path.join(dir, f);
      for (const x of (await Bun.file(nft).json()).files) files.add(path.resolve(path.dirname(nft), x));
    }
    const count = new Map();
    for (const f of files) {
      const m = f.match(/node_modules\/(@[^/]+\/[^/]+|[^@/.][^/]*)\//g);
      const pkg = m ? m.at(-1).slice(13, -1) : "(workspace)";
      count.set(pkg, (count.get(pkg) ?? 0) + 1);
    }
    console.log("traced files:", files.size);
    for (const [p, n] of [...count].sort((a, b) => b[1] - a[1]).slice(0, 40)) console.log(String(n).padStart(6), p);
    ' /tmp/next-trace/webapp-next-trace-*

    Mark each package on the Step 7 candidate list. Sum their files.

  3. Hosted window, from the same run's Build Verification job (second timestamp minus first):

    JOB=<Build Verification job id>
    gh api --allow-escape-sequences "repos/docs-plus/docs.plus/actions/jobs/${JOB}/logs" | sed 's/\x1b\[[0-9;]*m//g' \
      | grep -m2 -E 'Collecting build traces|Route \(pages\)'
  4. Docker split, for each deploy, from the job that holds 🏗️ Build Docker Images:

    JOB=<deploy or images job id>
    gh api --allow-escape-sequences "repos/docs-plus/docs.plus/actions/jobs/${JOB}/logs" | sed 's/\x1b\[[0-9;]*m//g' > /tmp/deploy.log

    After Try running the backend and webapp image builds at the same time #567, webapp lines carry [front] and the two builds share BuildKit ids. If grep -q '\[front\] ' /tmp/deploy.log succeeds, run grep '\[front\] ' /tmp/deploy.log | sed 's/\[front\] //' > /tmp/front.log and use /tmp/front.log below.

    LOG=/tmp/deploy.log   # or /tmp/front.log, as above
    STEP=$(grep 'webapp builder' "$LOG" | grep -m1 'next build' | awk '{print $2}')
    grep -E "^\S+ ${STEP} .*(Collecting build traces|next:build:build-traces|Route \(pages\))|^\S+ ${STEP} DONE" "$LOG"

    The third column is BuildKit seconds since the step began:

    • window = Route (pages) minus Collecting build traces;
    • trace part = finished build tracing minus Collecting build traces;
    • copy part = Route (pages) minus finished build tracing.
  5. Comment here, headed Measured, <YYYY-MM-DD> (before excludes):

    Source Run id Job id Window s Trace part s Copy part s next build s M3 s
    Docker … … … … … … …
    Docker … … … … … … …
    Hosted … … … — — — —

    Add the three hosted span seconds; node-file-trace-build starts at index.ts:2610-2611, before the window opens at :3826, so it is not a share of the window. Add the 📤 Upload webapp build trace seconds, the traced and candidate file totals and their ratio. Add the projected saving, median Docker window × candidate ratio, labelled "read from code, not measured".

Step 6. Apply the go rule

  • Go when the Step 5 projected saving is at least 30 s; span totals do not count. Continue at Step 7.
  • No go: comment here with the reason and the numbers. Skip Steps 7-9. The maintainer closes the issue as measured.

Step 7. Add outputFileTracingExcludes

File: apps/webapp/next.config.js. Insert right after outputFileTracingRoot (line 75):

  // Build-time packages the standalone server never loads. The '*' key also feeds
  // Next's trace-time ignore, so their files are neither walked nor copied.
  // Add an entry only with the start-and-route proof in AGENTS.md §Docker. Never sharp or @img.
  outputFileTracingExcludes: {
    '*': [
      '**/node_modules/rollup/**',
      '**/node_modules/@rollup/**',
      '**/node_modules/@sentry/cli/**',
      '**/node_modules/@sentry/cli-*/**'
    ]
  },

Write only globs whose package showed traced files in Step 5. The four above show the shape. Pick from this list only:

Glob Why it is a candidate
**/node_modules/rollup/**, **/node_modules/@rollup/** dependencies of @sentry/nextjs (bun.lock:1465) and a bundler
**/node_modules/@sentry/webpack-plugin/**, **/node_modules/@sentry/bundler-plugins/** Sentry's build plugins (bun.lock:1439, :1465, :1483)
**/node_modules/@sentry/cli/**, **/node_modules/@sentry/cli-*/** the sourcemap upload CLI and its native binaries (bun.lock:1441)
**/node_modules/webpack/**, **/node_modules/terser/**, **/node_modules/esbuild/**, **/node_modules/@esbuild/** bundlers and minifiers
**/node_modules/@swc/core/**, **/node_modules/@swc/core-*/**, **/node_modules/typescript/** compilers
**/node_modules/workbox-build/**, **/node_modules/next-pwa/** service worker generation at build time
**/node_modules/@babel/core/**, **/node_modules/sass/** coverage instrumentation (next.config.js:136-163) and style compile
  • Never exclude (runtime packages):
    • sharp, @img/*, next, @next/*, react, react-dom, styled-jsx, @swc/helpers;
    • @sentry/nextjs, @sentry/node, @sentry/core, @opentelemetry/*;
    • @supabase/*, @tiptap/*, prosemirror-*, yjs, y-*, lib0, @hocuspocus/*;
    • any @docs.plus/* package, and anything under apps/, packages/ or extensions/.
  • A package on neither list needs a maintainer ruling. Comment here with its file count.
  • Keep one key, '*'.
(cd apps/webapp && NODE_ENV=production NEXT_PUBLIC_RESTAPI_URL=http://localhost:4000 node -e "console.log(JSON.stringify(require('./next.config.js').outputFileTracingExcludes))" 2>/dev/null | tail -1)

On main it prints undefined; after the edit, the '*' key with your globs. Use node: on 2026-10-20, bun -e failed the workbox-runtime-caching.js self-check. If the value is not unchanged, stop and comment here.

Step 8. Prove it locally

Use make dev-backend, not make dev-local: it runs the webapp inside concurrently --kill-others-on-fail (Makefile:212-215), so stopping the webapp can stop REST, WS and the worker. bun scripts/check-next-server.ts must exit 0 and print nothing. Build "before" from HEAD and "after" with Step 7. Use cp, never git stash.

  1. Save your edit, then build "before", from the repo root:

    cp apps/webapp/next.config.js /tmp/next.config.after.js
    git show HEAD:apps/webapp/next.config.js > apps/webapp/next.config.js
    rm -rf apps/webapp/.next
    (cd apps/webapp && NODE_ENV=production bunx dotenv -e ../../.env.local -- bunx next build)
    find apps/webapp/.next/standalone \( -type f -o -type l \) | sort > /tmp/standalone-before.txt
    du -sh apps/webapp/.next/standalone
  2. Serve "before" as the image does (Dockerfile.bun:286, static and public beside it):

    cd apps/webapp
    cp -R public .next/standalone/apps/webapp/public
    cp -R .next/static .next/standalone/apps/webapp/.next/static
    for e in hyperlink hypermultimedia indent inline-code placeholder mermaid; do
      mkdir -p .next/standalone/extensions/extension-$e
      cp -R ../../extensions/extension-$e/dist ../../extensions/extension-$e/package.json .next/standalone/extensions/extension-$e/
    done
    NODE_ENV=production PORT=3999 HOSTNAME=127.0.0.1 bunx dotenv -e ../../.env.local -- bun .next/standalone/apps/webapp/server.js > /tmp/standalone.log 2>&1 &
    sleep 5
    SLUG="trace-check-$(date +%s)"
    for p in /api/health / "/${SLUG}" /c/trace-check "/_next/image?url=%2Ficons%2Ffavicon-16x16.png&w=32&q=75"; do
      printf '%s %s\n' "$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:3999${p}")" "$p"
    done | tee /tmp/codes-before.txt
    grep -c -E 'Cannot find module|MODULE_NOT_FOUND' /tmp/standalone.log
    pkill -f '.next/standalone/apps/webapp/server.js'
    cd ../..

    The loop copies what Dockerfile.bun:260-274 copies, after item 1 listed the trace.

  3. Restore your edit, build "after", and serve it the same way:

    cp /tmp/next.config.after.js apps/webapp/next.config.js
    git diff --stat -- apps/webapp/next.config.js   # expect only your Step 7 lines
    rm -rf apps/webapp/.next
    (cd apps/webapp && NODE_ENV=production bunx dotenv -e ../../.env.local -- bunx next build)
    find apps/webapp/.next/standalone \( -type f -o -type l \) | sort > /tmp/standalone-after.txt
    du -sh apps/webapp/.next/standalone

    Repeat the item 2 block with tee /tmp/codes-after.txt and the same SLUG shape.

  4. Compare:

    comm -13 /tmp/standalone-before.txt /tmp/standalone-after.txt | wc -l
    comm -23 /tmp/standalone-before.txt /tmp/standalone-after.txt \
      | sed -E 's#.*node_modules/((@[^/]+/)?[^/]+).*#\1#' | sort | uniq -c | sort -rn
    diff <(cut -d' ' -f1 /tmp/codes-before.txt) <(cut -d' ' -f1 /tmp/codes-after.txt)
    • The first count is 0.
    • Each removed package is excluded, or its dependency in bun.lock (grep -n '"<package>": \[' bun.lock), and not on the never-exclude list. A full path with no node_modules is a removed workspace file and fails. On a failure, stop and comment here.
    • The diff prints nothing. Expect 200 for /api/health, /, the slug and /_next/image, and 404 for /c/trace-check (200 in both if .env.local sets NEXT_PUBLIC_E2E=true).
    • Both grep -c runs print 0.
  5. Browser check on "after". Start it as in item 2. Open http://127.0.0.1:3999/<a new slug> in Chrome, type a heading and a line, and open the heading's chat. Pass: editor and chat render, with no failed chunk or module error.

  6. Clean up. Stop the server and run rm -rf apps/webapp/.next. Then stop make dev-backend and start make dev-local again.

  7. Comment here, headed Proof, <YYYY-MM-DD> (Step 8): both line counts, both du -sh sizes, the item 4 count and removed-package list, both code lists and the browser result.

Step 9. Docs for the exclude change

In the same change as Step 7, edit the "Only if Step 7 runs" files in Docs to update.

Review checkpoint

git diff -- apps/webapp/next.config.js AGENTS.md apps/webapp/CHANGELOG.md

Show the diff with the Step 5 and Step 8 comments, and stop for review. Then commit and push per SP2 (#554), as its own commit.

Acceptance criteria

  • 📤 Upload webapp build trace follows 🏗️ Build Webapp, pinned to 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a, with continue-on-error: true, timeout-minutes: 5, include-hidden-files: true and retention-days: 7.
  • One push run after Step 4 shows webapp-next-trace-<sha>, holding trace and at least one server/pages/*.nft.json.
  • The first webapp deploy after Step 4 prints starting build traces and finished build tracing <n>ms under the webapp next build step.
  • actionlint -config-file .github/actionlint.yaml .github/workflows/prod.docs.plus.yml prints nothing.
  • A Measured, <date> (before excludes) comment holds the Step 5 table for two deploys and one hosted run.
  • A comment here records go or no go with the numbers.
  • If go: next.config.js has one '*' key, and every glob is on the candidate list or has its own ruling comment.
  • If go: the Step 8 comment passes every item 4 check and the browser check.
  • If go: the after-excludes comment shows the keep rule result, and Step 7 is kept or reverted to match.
  • .github/WORKFLOWS.md lists the artifact. If Step 7 runs, AGENTS.md §Docker and apps/webapp/CHANGELOG.md record it in the same change.
  • If Step 7 is kept: the AGENTS.md bullet gives the before and after Docker windows and the deploy run ids. If reverted: the bullet and the CHANGELOG line are gone.

Verification

Before each hand-over:

actionlint -config-file .github/actionlint.yaml .github/workflows/prod.docs.plus.yml
docker build --check -f apps/webapp/docker/Dockerfile.bun .
bun run lint
bun run format
bun run check:agent-docs   # only when AGENTS.md changed (Step 9)

Triage and gating do not change, so skip parse-build-trigger.test.sh and dry_run_subject.

After the merge. Record per SP3 (#554); metrics are defined in #569.

Change Metric Read from Baseline Expected
Steps 1-2 M2g first push run after Step 4 the median M2g of the last 3 (build): deploys before Step 4, in the same "Ext suite ran" slice at most +10 s
Steps 1-2 M3 first webapp deploy after Step 4 median 546 s within the baseline range, 316–866 s
Steps 1-2 Docker window same deploy, Step 5 command 99.3–118.6 s, median 107.4 s no change
Step 7 Docker window first two webapp deploys after Step 7 the Step 5 median at least 20 s lower
Step 7 M3 and M2i same two deploys M3 median 546 s fall by about the window drop
Step 7 M1 same two deploys median 1090 s, p90 1386 s falls by about the window drop; after #565, only when M2i is later than M2g
Step 7 M4, M5, W same two deploys M4 0–72 s, M5 57–70 s no change
Step 7 Hosted window, and M6 next PR or non-deploy push hosted median 54.7 s; PR M6 median 519 s record only

Accountability

  • The builder posts here:
    • Measured, <YYYY-MM-DD> (before excludes): Step 5.
    • Proof, <YYYY-MM-DD> (Step 8): the local proof.
    • Measured, <YYYY-MM-DD> (after excludes): per SP3 ([Tracking] Faster, change-aware CI/CD pipeline #554), for the two deploys after Step 7, plus job ids, window, trace and copy parts, and the keep rule result.
  • The maintainer, within one hour of the first Step 7 deploy, checks and posts Checked, <YYYY-MM-DD>:
    • https://docs.plus/api/health returns 200, and https://docs.plus/c/trace-check returns 404, not 500.
    • A real pad loads in Chrome, takes typing, and opens a heading chat.
    • https://docs.plus/_next/image?url=%2Ficons%2Ffavicon-16x16.png&w=32&q=75 returns 200.
    • The webapp container log has no Cannot find module or MODULE_NOT_FOUND line.
    • 24 hours later, GlitchTip shows no new missing-module error.
  • Stop condition, revert Step 7 per SP4 ([Tracking] Faster, change-aware CI/CD pipeline #554) when any of these holds:
    • 🩺 Verify Deployment fails on a Step 7 deploy (it rolls back by itself; revert before the next deploy).
    • Any maintainer check above fails.
    • The median Docker window of the first two Step 7 deploys is less than 20 s below the Step 5 median.
  • Stop condition for Steps 1-2: Build Verification grows by more than 30 s, or either change makes a job fail.

Rollback

Revert per SP4 (#554).

  • Steps 1-3: no image content changes, so no deploy is needed. The artifacts expire after 7 days.
  • Step 7: deploy the revert with the tip subject (build): front. Never no-deploy, observability or a bare (build):; none deploys the webapp. The revert also removes the Step 9 AGENTS.md bullet and CHANGELOG line.
  • No host step and no GitHub setting changes in this issue.

Docs to update

  • .github/WORKFLOWS.md §Active workflows, workflows/prod.docs.plus.yml entry: the artifact and the debug lines (Step 3).
  • Only if Step 7 runs:
    • AGENTS.md §Docker: one bullet with the rule and the never-exclude list, no numbers yet. The rule: "An outputFileTracingExcludes entry ships only after two proofs. A file-list diff removes only the excluded packages and their own dependencies. A standalone start serves /api/health, a pad and /c/<id> with the same codes as before." Run bun run check:agent-docs.
    • apps/webapp/CHANGELOG.md [Unreleased] ### Internal: one line, "The standalone build no longer traces or ships build-only packages: ."
  • After the keep rule passes: a second hand-over adds the before and after Docker windows and run ids to that bullet. Show git diff -- AGENTS.md, run bun run check:agent-docs, stop for review, then commit and push per SP2 ([Tracking] Faster, change-aware CI/CD pipeline #554).

Out of scope

Read first

  • AGENTS.md §Package Manager, §Git And Commits, §Test Policy (never next build against a live dev server) and §Docker.
  • apps/webapp/CLAUDE.md.
  • .github/WORKFLOWS.md and .github/workflows/prod.docs.plus.yml, jobs build and deploy.
  • apps/webapp/next.config.js and apps/webapp/docker/Dockerfile.bun, in full.
  • Next 15.5.27 sources: packages/next/src/build/collect-build-traces.ts and packages/next/src/build/index.ts, lines 3820-4045.
  • The tech-writer skill, for the comments and the AGENTS.md bullet.

Activity

  1. added this to the v2.3.0 milestone on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions