Skip to content

🤖 perf: keep toolDefinitions out of the main chunk (#7363) - #7364

Closed
ThomasK33 wants to merge 1 commit into
mainfrom
perf/ask-question-schemas-main-chunk
Closed

ThomasK33 wants to merge 1 commit into
mainfrom
perf/ask-question-schemas-main-chunk

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes #7363. #6887 made the eager renderer entry chunk (main-*.js) grow by 92 KB raw / 23.7 KB br. The cause was not the ask_user_question dock: openAskUserQuestions.ts, which StreamingMessageAggregator loads eagerly, imported TOOL_DEFINITIONS only to call TOOL_DEFINITIONS.ask_user_question.schema.safeParse. That pulled toolDefinitions.ts and its imports (every tool schema) out of the lazy toolCardsLoader chunk into main. This PR moves the three ask_user_question argument schemas to a zod-only module, so main is back within 750 B br of the build before #6887.

Background: step 0 (bundle diff)

I built the renderer (make build-renderer, Bun 1.4.3) at 32b80b2 (before #6887) and at 75d4c2b (#6887), and attributed main-*.js bytes per source through its source map (sources + generated columns).

Modules that #6887 added to main-*.js (generated bytes in main), and where they are on this head:

Module Added by #6887 This head
src/common/utils/tools/toolDefinitions.ts 71,326 gone
src/common/orpc/schemas/workspace.ts 14,303 gone
src/common/analytics/schemaSql.ts 1,489 gone
src/common/orpc/schemas/artifacts.ts 1,186 gone
src/common/orpc/schemas/workspaceAiSettings.ts 659 gone
src/browser/utils/messages/openAskUserQuestions.ts 636 stays (611 B): the open-question selector itself, which the aggregator's truncation rule runs
src/common/utils/artifactKind.ts 334 gone
src/common/types/workspaceTurn.ts 316 gone
src/common/utils/tools/toolInputFilePath.ts 205 gone
src/common/constants/computerUse.ts 204 gone
src/common/constants/contextBudget.ts 181 gone
src/common/config/schemas/configOperations.ts 136 gone
src/common/constants/memory.ts 115 gone
src/common/types/tools.ts 75 gone
node_modules/zod-to-json-schema/dist/esm/parsers/string.js 72 gone
src/common/utils/tools/toolCatalog.ts 18 gone
src/common/constants/subagentLifecycle.ts 17 gone
src/constants/agentMessaging.ts 13 gone
src/constants/taskMessages.ts 11 gone

Implementation

  • New src/common/utils/tools/askUserQuestionSchemas.ts (imports only zod) holds AskUserQuestionOptionSchema, AskUserQuestionQuestionSchema and AskUserQuestionToolArgsSchema, moved unchanged from toolDefinitions.ts.
  • toolDefinitions.ts imports and re-exports them, so its callers (common/types/tools.ts, workspaceService.ts, …) do not change. TOOL_DEFINITIONS.ask_user_question.schema is the same object as the new module's AskUserQuestionToolArgsSchema.
  • openAskUserQuestions.ts parses with that schema directly and no longer imports toolDefinitions.ts.
  • ask_user_question behavior, answer storage and the aggregator truncation rule do not change.

Validation (gates declared by the perf owner before measuring)

  1. Module gate. Source-map diff of main-*.js: vs 75d4c2b, 18 of the 19 modules are gone, and toolDefinitions.ts is back in toolCardsLoader-*.js. openAskUserQuestions.ts stays (see the table). A grep for a toolDefinitions.ts string (Requires UI interaction) in main-*.js finds 1 hit at 75d4c2b and 0 here. New eager code vs 32b80b2: +1,751 B generated in total (askUserQuestionSchemas.ts 819, openAskUserQuestions.ts 611, 🤖 feat: keep open ask_user_question forms sticky above the chat input #6887's own askUserQuestionSummary.ts +113 and StreamingMessageAggregator.ts +106, and 102 B of identifier noise in files with no source change). Budget 2,048 B.

  2. Main chunk size (local builds, same setup):

    Build main-*.js raw main-*.js.br
    32b80b2 (before 🤖 feat: keep open ask_user_question forms sticky above the chat input #6887) 1,730,484 302,991
    75d4c2b (🤖 feat: keep open ask_user_question forms sticky above the chat input #6887) 1,822,614 326,714
    this head 1,732,235 303,741 (+750 vs base, budget +1,024)

    bun scripts/perf/firstLoadJs.ts dist, total of 4 first-load files (raw / br / gz KiB): base 2,190.4 / 434.1 / 533.0, 75d4c2b 2,280.4 / 457.1 / 561.2, head 2,192.2 / 434.7 / 533.7. The emitted chunk set (715 chunks, names without hashes) is the same as base's, so this adds no second-wave request.

  3. Tests. One new contract test: TOOL_DEFINITIONS.ask_user_question.schema is the light module's AskUserQuestionToolArgsSchema (the renderer selector and the backend must validate with the same schema). It fails when the tool uses a copy (.describe("copy")). The existing malformed-args tests (askUserQuestionPlacement.test.ts, StreamingMessageAggregator.test.ts) exercise the new parse path and pass unchanged. A separate test audit found no low-value tests and no test-only exports. Guard to keep toolDefinitions.ts out of main: firstLoadJs.ts --forbid toolDefinitions.ts catches it (it flags 75d4c2b), but no CI job runs firstLoadJs.ts with a forbid list. Adding one would be a new mechanism, so I left it out. The perf owner will file a follow-up.

  4. make static-check, make lint-eslint, make check-react-compiler (24/25, unchanged), make crap-check, make dry-check and the ask_user_question / aggregator / tool test files are green.

  5. Dogfood. make dev-server-sandbox with a seeded workspace holding an open non-blocking ask_user_question. The dock shows the form, and the transcript row shows the stub ("Answer in the panel above the chat input.").

Dock with open question, desktop 1440 px

Dock with open question, 390 px

Follow-ups

Risks

Low. The change moves schema definitions without edits, and re-exports keep every import path working. If a future edit redefines the schema in toolDefinitions.ts, the contract test fails.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $2.10

openAskUserQuestions.ts (eager via StreamingMessageAggregator) imported
TOOL_DEFINITIONS only to parse ask_user_question args, which put every
tool schema into main-*.js. Move the ask_user_question arg schemas to
askUserQuestionSchemas.ts (zod only); toolDefinitions re-exports them.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T19:16:17.280509Z fd6b719 Manual request
🔒 Security Review ✅ Completed 2026-10-23T19:17:04.743739Z fd6b719 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

Perf owner gate decision (head fd6b719): I accept openAskUserQuestions.ts (611 B) staying in main-.js as a gate-1 exception. It is #6887's own selector, which the aggregator truncation rule runs, and its bytes count inside the 1,751 B new-eager-code budget (limit 2,048 B). The other 18 modules must stay out of main, and main-.js.br must stay within +1,024 B of 32b80b2's on the final head.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: fd6b7197e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

Copy link
Copy Markdown
Contributor

🟢 e2e stories (Haiku): 14 passed

27 agent steps · 7 replayed from cache · 31 model calls · 221.9k tokens (70% cached)
Spend: $0.10 of the $1.00 cap (proxy ledger)

All 14 tests in 9 files
Test Agent Time
🟢 chatRestart.e2e.ts · 1 passed 2 steps · 4 calls 24.1s
🟢 a chat survives a restart of the server 2 steps · 4 calls 24.1s
🟢 compaction.e2e.ts · 1 passed 3 steps · 7 calls 43.3s
🟢 /compact replaces the old chat with a summary 3 steps · 7 calls 43.3s
🟢 pluginTrustRestart.e2e.ts · 1 passed 4 steps · 2 calls 10.3s
🟢 a trusted plugin hook stays trusted after the server restarts 4 steps · 2 calls 10.3s
🟢 settingsRoundTrip.e2e.ts · 1 passed 3 steps · 4 calls 29.0s
🟢 a chat survives trips through Settings 3 steps · 4 calls 29.0s
🟢 sidebarPerWorkspace.e2e.ts · 1 passed 4 steps · 1 call 4.3s
🟢 a new workspace starts with its right sidebar collapsed while another keeps it open 4 steps · 1 call 4.3s
🟢 smoke.e2e.ts · 1 passed 2 steps · 1 call 3.8s
🟢 the agent opens the playground workspace 2 steps · 1 call 3.8s
🟢 streamErrors.e2e.ts · 1 passed 1 step · 1 call 6.9s
🟢 stream errors show in the chat, and the chat recovers 1 step · 1 call 6.9s
🟢 todoNudge.e2e.ts · 1 passed 2 steps · 5 calls 24.3s
🟢 the agent finishes open TODO items after the turn-end nudge 2 steps · 5 calls 24.3s
🟢 touchAlignment.e2e.ts · 6 passed 6 steps · 6 calls 34.2s
🟢 the Settings close button is level with the title on a touch screen 390px wide 1 step · 1 call 4.4s
🟢 the Settings close button is level with the title on a touch screen 768px wide 1 step · 1 call 3.9s
🟢 Backup settings checkboxes are small and level with their labels on a touch phone 1 step · 1 call 6.4s
🟢 the heartbeat checkbox is small and level with its label on a touch phone 1 step · 1 call 11.8s
🟢 a reply's model and time stay on one line on a touch phone 1 step · 1 call 4.6s
🟢 the workspace actions menu stays on the title row on a touch phone 1 step · 1 call 3.1s

e2e 0.20.0 · 3m 3s · web · run artifacts

🟢 e2e Electron stories (Haiku): 3 passed

3 agent steps · 3 model calls · 8.0k tokens (21% cached)
Spend: $0.02 of the $1.00 cap (proxy ledger)

All 3 tests in 3 files
Test Agent Time
🟢 electron/deepLink.e2e.ts · 1 passed 1 step · 1 call 5.1s
🟢 a deep link of a second launch starts a chat in the running app 1 step · 1 call 5.1s
🟢 electron/menuBar.e2e.ts · 1 passed 1 step · 1 call 8.5s
🟢 the workspace menu bar is the desktop title bar 1 step · 1 call 8.5s
🟢 electron/smoke.e2e.ts · 1 passed 1 step · 1 call 5.2s
🟢 Xum Electron opens the playground workspace 1 step · 1 call 5.2s

e2e 0.20.0 · 20.8s · electron · run artifacts

🟢 e2e agent repros (Haiku): no tests selected

Spend: $0.00 of the $1.00 cap (proxy ledger)

e2e 0.20.0 · 5.3s · 2 targets (web, phone) · run artifacts

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: fd6b7197e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: fd6b7197e1

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33

Copy link
Copy Markdown
Member Author

Perf owner readiness record for head fd6b7197e1. Decision: READY with tracked follow-ups. I am enqueueing the PR.

  1. Lighthouse VM A/B (seeded, 6+6 ABBA, 24/24 quiet; A = 75d4c2b, B = fd6b719; folders runs-ab/20261023T192307Z mobile and runs-ab/20261023T192848Z desktop). I read these numbers from the folders:
    • Seeded mobile TBT median: 241.5 -> 204.5 ms (-37.0; rule <= -15).
    • Seeded desktop LCP median: 1609.8 -> 1560.2 ms (rule <= +40).
    • Seeded mobile LCP median: 1957.5 -> 1956.8 ms (rule <= +100).
    • Desktop API long task at ~845 ms: A 3/6, B 0/6 (rule <= 1/6).
    • All four conditions of the merge rule I declared before the run hold.
  2. Byte gates (lane's local builds): main-*.js.br is 303,741 B vs 302,991 B before 🤖 feat: keep open ask_user_question forms sticky above the chat input #6887 (+750, budget +1,024). 18 of 19 modules left main; openAskUserQuestions.ts is the accepted exception (comment above).
  3. Review: Codex code and security reviews completed on this head with no findings, 👍, 0 threads. Required passes. Rounds used: 2 of 6.
  4. Independent advisor (clean context): READY. Same schema object, re-exports keep the existing importers working, 4 files only. Pixel / Review has 1 snapshot pending; it is not in Required and this PR changes no UI.
  5. Follow-up: perf: CI guard that keeps toolDefinitions.ts (and other heavy modules) out of the renderer main chunk #7365 (CI guard that keeps toolDefinitions.ts out of the main chunk), owner perf owner.

Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high

@ThomasK33
ThomasK33 added this pull request to the merge queue Oct 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 23, 2026
@ThomasK33

Copy link
Copy Markdown
Member Author

Perf owner: the merge queue removed this PR at 19:46Z (merge-group run 40012690165). The only failing job is Test / Stories / make test-stories: sandbox refused: stale image: image.json has inputs key 08a81da3ddeb1ec1, this checkout 2bf9e12d3ac7dcb1. #7362 changed the sandbox Dockerfile, and its image.json step has not landed yet (#7360 was dequeued for the same reason). This PR does not cause it. I will re-enqueue once after tests/bugbash/sandbox/image.json changes on main.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high

@ThomasK33

Copy link
Copy Markdown
Member Author

Perf owner: closing as superseded. #7369 (merged 2633118) cherry-picked this PR's commit together with the sandbox image pin. git diff fd6b7197e1 2633118d2c -- src/ is empty, so the code measured in the Lighthouse A/B above is what landed on main.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high

@ThomasK33 ThomasK33 closed this Oct 23, 2026
@ibetitsmike

Copy link
Copy Markdown
Contributor

Your commit fd6b719 is on main now: #7369 carried it unchanged (same patch-id), and the squash commit 2633118 has a Co-authored-by trailer for you. #7363 closed with that merge.

Main was deadlocked. This PR alone failed the queue's story jobs on the stale sandbox image key from #7362, and the image pin alone failed rendererFirstLoad.test.ts, so both went in one PR. This PR has nothing left to merge, so it can be closed.

azaj01 pushed a commit to azaj01/mux that referenced this pull request Oct 25, 2026
…2.23.4 sandbox image (coder#7369)

## Summary

This PR fixes the two problems that keep main red, in one PR, because
each fix fails CI without the other. The first commit is Thomas's fix
from coder#7364 (cherry-picked, his authorship kept). It moves the
ask_user_question argument schemas out of `toolDefinitions.ts`, so the
renderer entry chunk no longer pulls in every tool schema and
`rendererFirstLoad.test.ts` passes again. The second commit pins the
bug-bash sandbox image that main published from coder#7362 (base
`node:22.23.4-slim`, the CVE-2026-111497 fix), so the model-driven story
jobs run again on main and in the merge queue. Fixes coder#7363.

## Background

- coder#6887 made `openAskUserQuestions.ts`, which the eager
`StreamingMessageAggregator` loads, import `TOOL_DEFINITIONS`. That
moved `toolDefinitions.ts` and its imports into `main-*.js` (+92 KB raw,
+23.7 KB br) and fails `Test / Unit (5/6)`: "renderer first load > the
entry's static imports do not reach the tool definitions module".
- coder#7362 changed the sandbox Dockerfile, so the inputs key on main is now
`2bf9e12d…`, but `image.json` still pins the old image. `make
test-stories` refuses the old record ("sandbox refused: stale image:
image.json has inputs key 08a81da3ddeb1ec1, this checkout
2bf9e12d3ac7dcb1"). `Test / Stories` and `Test / Stories (Haiku)` count
in `Required` (`STORIES_MODE` and `STORIES_PR_MODE` are `block`), so the
queue removed coder#7360 and coder#7364 on that failure.

So coder#7364 alone fails the story jobs, and the pin alone fails the unit
test. The queue groups entries with `ALLGREEN`, so neither can merge
first. This PR carries both.

## Implementation

- `perf: keep toolDefinitions out of the main chunk (coder#7363)` is
fd6b719 from coder#7364 with no change (the same `git patch-id`). The new
`src/common/utils/tools/askUserQuestionSchemas.ts` imports only zod and
holds the three ask_user_question schemas. `toolDefinitions.ts` imports
and re-exports them, and `openAskUserQuestions.ts` parses with
`AskUserQuestionToolArgsSchema` directly. coder#7364 has the bundle
measurements: the main chunk is back within 750 B br of the build before
coder#6887. coder#7364 can close after this merges.
- `tests/bugbash/sandbox/image.json` gets this record, written with the
same `jq -n` command that `build.sh --push` uses:

| Field | Value |
| ---------------- |
----------------------------------------------------------------------------------------------------------------------------
|
| `digest` |
`sha256:e3804b5418a8e6783698d807c75cc256efae45b81b03400644ae7f13364c9a03`
|
| `inputsKey` |
`2bf9e12d3ac7dcb10397698df2792bf1b244b3fcbe68225378302ef695ba302a`, what
`make -s bugbash-sandbox-key` prints on this commit |
| `playwrightCore` | `1.63.0` (unchanged) |
| `publishedFrom` | `2e2a2b769f417aedcecc09f33abb379d6c8be7b1` (main,
the coder#7362 merge) |

## Validation

- `rendererFirstLoad.test.ts` fails on the pin commit alone (21f9c3f)
and passes with the cherry-pick.
- Publish run on main:
[40016150145](https://github.com/coder/xum/actions/runs/40016150145),
from `2e2a2b769f`. The digest is the subject of its build provenance
attestation.
- `gh attestation verify
oci://ghcr.io/coder/xum-bugbash-sandbox@sha256:e3804b54… --repo
coder/xum` passes. The signer is
`.github/workflows/bugbash-sandbox-image.yml@refs/heads/main`
(workflow_dispatch, run 40016150145), and the source commit is
`2e2a2b769f`.
- I pulled the image by digest. It is linux/amd64, its
`org.xum.bugbash.inputs` label is `2bf9e12d…a302a`, its
`org.xum.bugbash.playwright-core` label is `1.63.0`, and `node -v` in it
prints `v22.23.4`.

Co-authored-by: Thomas Kosiewski <tk@coder.com>

---------

Co-authored-by: Thomas Kosiewski <tk@coder.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: #6887 ask_user_question dock adds ~24 KB to the entry chunk and slows seeded load (TBT +40 ms mobile, LCP +52 ms desktop)

2 participants