Skip to content

web-lint: hosts that run no heavy checks leave the type-aware lint to CI - #27177

Merged
teamleaderleo merged 16 commits into
feat-cmux-nextfrom
fleet/prepush-heavy-lint-no-build-hosts
Oct 30, 2026
Merged

teamleaderleo merged 16 commits into
feat-cmux-nextfrom
fleet/prepush-heavy-lint-no-build-hosts

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The pre-push web lint runs webviews' whole bun run check. Two of its pieces, vp lint and vp check --no-fmt --no-lint, are type-aware (config/vite-plus/check.ts: typeAware, typeCheck) and start tsgolint: about 9 GB and 5 GB, running at the same time. On hosts the fleet registry marks as no-build (shared agent hosts), every push of a webviews change ran both locally. That is a heavy local run on a host where they are banned, and it filled swap there about every three hours.

After this change:

  • A host that sets CMUX_LOCAL_HEAVY_CHECKS=0 runs only an allowlist (LIGHT_COMMANDS), named exactly:

    • vp fmt --check;
    • python3 source checks;
    • bun scripts/<name>.ts source checks;
    • ESLint on changed web/ files (syntax only).

    bun run NAME is resolved to NAME's own pieces, and only the allowlisted ones run, so check:effect-cleanup's vp lint is skipped. The host does no install, no build (the tui bindings build is npm ci plus tsc) and no --new-only base comparison. Every skipped piece, and any package with no node_modules, is named in the output with the cmux-ci test -- bash -c 'cd webviews && bun install --frozen-lockfile && bun run check' command for a build box. A light failure still refuses the push.

  • Everywhere else, every tool the script starts (installs, builds, checks, base reruns) goes through one spawn(). Outside CI it runs the tool under nice -n 10 with GOMAXPROCS (half the cores, at most 8), GOMEMLIMIT=6GiB and --max-old-space-size=4096, each set only when the caller did not set one. CI runs unchanged.

The script reads only the environment variable and names no hosts. The fleet's own hook wrapper sets the variable from the registry's per-host build flag, so host lists stay out of this repo.

The web lint (fast) job on every feat-cmux-next push and the PR fast gates still run the full check.

Testing

python3 tests/test_web_lint_prepush.py: all 40 tests pass on the head. The new ones:

  • test_no_mode_spawns_more_than_the_allowlist_on_a_host_that_runs_no_heavy_checks: with heavy=0, a subprocess.run spy sees only allowlisted commands in every mode: --head on the checkout, another commit (lint tree), --new-only on a red change, --all and --fix. No install, build, tsc or vp lint/check runs.
  • test_a_host_that_runs_no_heavy_checks_runs_only_the_allowlist and test_light_pieces_still_refuse_a_bad_push_...: a real git push through the installed hook. A nested bun run script with a vp lint inside is skipped, and a light failure still refuses the push.
  • test_a_host_that_runs_no_heavy_checks_installs_and_builds_nothing and test_a_lint_tree_..._shares_the_frontend_node_modules: the tui frontend case.
  • test_every_spawn_on_a_build_host_is_niced_and_capped, test_a_build_host_runs_every_piece_niced_and_capped, test_new_only_reruns_a_piece_on_the_base_niced_and_capped and test_the_bindings_build_runs_niced_and_capped_outside_ci: everything is niced and capped on a build host.
  • test_the_allowlist_is_exact and test_ci_spawns_as_is_and_a_caller_cap_is_kept.

Observed / Not run

Observed: each new test fails on the previous heads (the allowlist and spawn tests on f9399db, the rest on fe31ba9 or 6160990). All 40 pass at the head. A dry run on the real packages with heavy=0 runs only the formatter check and the python3 and bun source checks; it names vp lint (both), vp check and the effect-cleanup vp lint as left to CI.
Not run: a real bun run check on a build host. The stand-ins check niceness and env, not tsgolint's real peak memory.

Changelog

none

Proof

No capture: pre-push tooling only, no UI.

Checklist

  • Behavior changes have added or updated tests, or Testing says why not
  • Reviewed with a subagent before merge

🤖 Generated with Claude Code

Agent session: agent session

The pre-push web lint runs webviews' whole `check` script, whose `vp lint` and `vp check`
start tsgolint (about 9 GB and 5 GB, both at once). On shared agent hosts that run no
builds, that filled swap. A host that sets CMUX_LOCAL_HEAVY_CHECKS=0 now runs every other
piece locally and leaves the heavy ones to CI's web lint job and the pull request's fast
gates, saying so and naming cmux-ci test for a pre-push run. Wherever a heavy piece does
run outside CI, it runs under nice with GOMAXPROCS, GOMEMLIMIT and Node's heap capped,
each only when the caller set none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 1e255ee: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr27177 -f ref=fleet/prepush-heavy-lint-no-build-hosts -f platform=macos -f backend_mode=local

web and size checks: green

@teamleaderleo

teamleaderleo commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-nw4vff created for this PR by the cmux coordinator.

Review of 6160990: with CMUX_LOCAL_HEAVY_CHECKS=0 the tui bindings were still installed and
built (npm ci, npm run build: tsc) before the check pieces were filtered. Now such a host
installs and builds nothing: light pieces run against the node_modules a package already has,
and a package with none is left to CI with a named note. Regression test: a cmux-tui/frontends/web
change with CMUX_LOCAL_HEAVY_CHECKS=0 spawns no npm, bun install, build, tsc or vp; it fails on
6160990 (npm ci and npm run build ran).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc
…stall without heavy checks

Review of 5c38839:
- the tui bindings build (npm ci, tsc) runs niced and capped outside CI, like the type check;
- --new-only reruns a failed piece on the base through the same niced, capped job;
- with CMUX_LOCAL_HEAVY_CHECKS=0, --new-only installs nothing in the base tree (no node_modules
  there: the red stays the change's), and a lint tree shares the checkout's node_modules for
  packages with local builds too, since nothing is built.
Four regression tests; each fails on 5c38839.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc
@cursor

cursor Bot commented Oct 29, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@blacksmith-sh

blacksmith-sh Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Found 7 test failures on Blacksmith runners:

Failures

Test View Logs
main.DeveloperIdAppTests.test_download_prints_the_active_profile_only/
test_download_prints_the_active_profile_only
View Logs
stubs: /System/Library/Frameworks/Security.framework/Versions/A/Security has no stub/
Security has no stub
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_CFRetain']/
CoreFoundation, missing 1: ['_CFRetain']
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_log2']/
CoreFoundation, missing 1: ['_log2']
View Logs
test_a_host_that_runs_no_heavy_checks_installs_and_builds_nothing (main.TuiBindings
Tests.test_a_host_that_runs_no_heavy_checks_installs_and_builds_nothing)/
test_a_host_that_runs_no_heavy_checks_installs_and_builds_nothing (main.TuiBindings
Tests.test_a_host_that_runs_no_heavy_checks_installs_and_builds_nothing)
View Logs
test_a_lint_tree_on_a_host_that_runs_no_heavy_checks_shares_the_frontend_node_modules (
main.TuiBindingsTests.test_a_lint_tree_on_a_host_that_runs_no_heavy_checks_shares_t
he_frontend_node_modules)/
test_a_lint_tree_on_a_host_that_runs_no_heavy_checks_shares_the_frontend_node_modules (
main.TuiBindingsTests.test_a_lint_tree_on_a_host_that_runs_no_heavy_checks_shares_t
he_frontend_node_modules)
View Logs
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)/
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…spawn for everything

Five review rounds each found another path that still ran something heavy with
CMUX_LOCAL_HEAVY_CHECKS=0, so the denylist is gone. Such a host now runs only LIGHT_COMMANDS:
`vp fmt --check`, python3 source checks, and `bun scripts/<name>.ts` source checks. `bun run NAME`
is resolved to NAME's pieces and only the allowlisted ones run, so check:effect-cleanup's
`vp lint` is skipped. ESLint runs on changed web/ files (syntax only). It does no install, no
build and no --new-only base comparison; every other piece, and a package with no node_modules,
is named and left to CI or cmux-ci.

Every tool this script starts (installs, builds, checks, base reruns) goes through spawn(),
which outside CI runs it under nice with GOMAXPROCS, GOMEMLIMIT and Node's heap capped.

Tests: with heavy=0, a subprocess spy sees only allowlisted commands across --head, a lint tree
(another commit), --new-only, --all and --fix. On a build host every spawn is niced and capped.
The allowlist is checked exactly. Each new test fails on f9399db.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc
@cursor

cursor Bot commented Oct 29, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

…is never light

Review of 5a4516a: an allowlisted prefix could carry another command (`python3 noop.py ; vp lint`
ran the lint with CMUX_LOCAL_HEAVY_CHECKS=0), because accepted strings ran with shell=True. Now a
piece holding any shell syntax (; | & $ backticks redirections globs quotes escapes newlines) is
never light. An allowlisted piece is parsed with shlex.split, checked as an argv, and run as that
argv with shell=False. Tests cover ;, &&, ||, |, &, $(...), backticks, newlines and the
reviewer's repro through the full main(), and assert no light piece spawns through a shell;
each fails on 5a4516a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc
Review of 5eb2e77: `nice -n 10 CHECK_MODE=1 python3 check.py` ran CHECK_MODE=1 as the program
(exit 127), and `nice -n 10 true; python3 x.py` niced only `true`. A shell string now runs as
`nice -n 10 /bin/sh -c STRING`, so assignments and every command of a compound string keep their
meaning and their priority. Regression test fails on 5eb2e77. The bindings-build test now clears
CI and GITHUB_ACTIONS itself, since it asserts outside-CI behaviour (it failed on the CI runner).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc
…cript

Review of 3811e05: any shell-free `python3 ...` counted as light, so `python3 -m pip install ..`,
`python3 -m compileall` or a bare `python3` ran with CMUX_LOCAL_HEAVY_CHECKS=0. Now the
allowlist is `vp fmt --check` exactly, plus `python3 [-I] PATH.py ARGS` or `bun PATH.ts ARGS`,
where PATH resolves (relative to the package, no escape) into a scripts/ folder under one of
the package's `executed` paths, which the hook already refuses to lint when a push changes them.
No -m, -c, other flags, absolute paths or bare interpreter. webviews' real checks all still
qualify. On 3811e05, is_light("python3 -m pip install requests") is True; the new test fails there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, next-review): SHIP at 91644bb

No blocking findings.

Review of 91644bb: with CMUX_LOCAL_HEAVY_CHECKS=0 the dry run skipped light_plan(), so it listed
packages and ESLint areas a run drops for having no node_modules. A run and a dry run now build
their commands with one function, check_jobs(), over the same light_plan(): the dry run checks the
node_modules the run would see (in a lint tree only where shares_deps() holds). It prints one
`<area>: <command>` line per command. A new test runs both in light mode, on the checked-out head
and on another commit, and requires the dry run's list to equal the spawned list. It fails on 91644bb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-7a592ef92c66f080e3cc
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex-review-box/gpt-6-astra high, lane-place): BLOCK at c146f28

BLOCK

  1. [P2] Dry-run omits builds and installs required in temporary worktrees — scripts/web_lint_prepush.py:890. plan_steps() checks the checkout’s build stamps and dependencies even when execution will use a fresh worktree. Reproduced with current TUI artifacts and a dirty checkout: dry-run lists only the check, while execution runs npm ci, npm run build, and frontend bun install first. Plan setup against the target worktree’s expected state so dry-run accurately exposes these operations.

@teamleaderleo teamleaderleo removed the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 29, 2026
A dry run of a commit that runs in a fresh lint tree read the checkout's build
stamps and node_modules, so it listed only the check while the run built the
bindings and installed the frontend first. setup_steps() and plan_steps() now
take a TreeState: the checkout as it is, or the lint tree tree_of() makes (no
build output, node_modules only where it links the checkout's).

Test: TuiBindingsTests.test_a_dry_run_for_a_lint_tree_lists_the_builds_and_installs_that_tree_needs
fails on c146f28 (dry run lists only `sh check.sh`) and passes here;
tests/test_web_lint_prepush.py: 48 tests OK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-a7306ef145c005ad24ab
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, fast, review-router; drain-other): SHIP at 1f157f0

[P3] Preserve Node heap limits using underscore syntax — scripts/web_lint_prepush.py:406. Node accepts NODE_OPTIONS=--max_old_space_size=1024, but this check misses it and appends --max-old-space-size=4096, overriding the caller’s limit. A Node probe confirmed the effective heap limit increases. Recognize both spellings before adding the default.

teamleaderleo and others added 2 commits October 29, 2026 16:23
Conflict: tests/test_web_lint_prepush.py imports (kept both io and json).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3aa8a14a36831eaec813
… NAME:pieces

Since #24438 webviews' check is run-check-pieces.py over check:pieces. check_pieces()
already reads SCRIPT:pieces; light_leaves() now does the same for `bun run NAME`, so a
nested runner is never run whole as one (light-looking) python3 command. Tests: a
heavy=0 push with the runner-shaped check runs only the allowlisted pieces, and a
nested runner resolves to its pieces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3aa8a14a36831eaec813
@cursor

cursor Bot commented Oct 29, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, fast, review-router; drain-other): BLOCK at 014ded9

[P2] scripts/web_lint_prepush.py:903 uses local_heavy_checks() even in CI. With the documented CMUX_LOCAL_HEAVY_CHECKS=0 host setting, a CI run executes only the light allowlist and skips the full type-aware checks, contradicting the PR’s claim that CI always runs the full check. CI should force heavy checks.

[P3] scripts/web_lint_prepush.py:491 rejects any shutil.which("bun") result that is not absolute. Valid relative PATH entries can produce paths such as node_modules/.bin/bun, causing installs to be reported as “bun is not on PATH”; the previous implementation accepted these paths.

…ive PATH entry

Review of 014ded9: CMUX_LOCAL_HEAVY_CHECKS=0 inherited by a CI runner made CI run only
the light allowlist. CI (CI or GITHUB_ACTIONS=true) now wins over the host setting.
ensure_deps() refused any bun that shutil.which() found through a relative PATH entry
(node_modules/.bin/bun) as "bun is not on PATH"; it now refuses only when which() finds none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3aa8a14a36831eaec813
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, fast, review-router; drain-other): BLOCK at 9fee512

[P2] run_plan skips prepare_builds whenever setup_steps() returns an empty list (scripts/web_lint_prepush.py:711). If a local bindings build is stale, neither npm nor bun is available, and the dependent package already has node_modules without a bun.lock, setup_steps() is empty and the code proceeds to lint against stale bindings instead of returning ensure_build’s intended failure. This can let frontend type checks pass against an old SDK.

Review of 9fee512: run_plan() ran prepare_builds() only when setup_steps() listed a
spawn. With neither npm nor bun, a stale bindings build spawns nothing, and a frontend
with node_modules and no bun.lock installs nothing, so the frontend check ran against
the old bindings. With heavy checks on, run_plan() now always goes through
prepare_builds() (ensure_build's failure surfaces), and setup_steps()/plan_steps() list
that refusal and no checks after it, so --dry-run matches the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3aa8a14a36831eaec813
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, fast, review-router; drain-other): SHIP at 1b67ec3

No blocking findings.

CI now always runs every piece (CI or GITHUB_ACTIONS=true wins over
CMUX_LOCAL_HEAVY_CHECKS=0), so the bindings tests that mean "a host with no heavy
checks, outside CI" inherited the runner's GITHUB_ACTIONS=true and ran the full
install and build on CI. They now pass LIGHT_HOST, which clears both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3aa8a14a36831eaec813
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, fast, review-router; drain-other): SHIP at 1e255ee

[P3] --dry-run can advertise a bun install that execution will never start. setup_steps() gets ["bun", ...] from deps_command() even when Bun is unavailable (web_lint_prepush.py:478), while ensure_deps() returns an error before calling spawn() (web_lint_prepush.py:489). Thus dry-run output is not an accurate description of the actual run in a host without Bun.

[P3] No-heavy deferral output does not include the required build-box command for packages without node_modules (web_lint_prepush.py:657). The PR description says every such package is named together with a cmux-ci test ... command, but these messages only point to CI, leaving users without the documented reproduction command.

@teamleaderleo
teamleaderleo merged commit 1b67652 into feat-cmux-next Oct 30, 2026
132 of 135 checks passed
@teamleaderleo
teamleaderleo deleted the fleet/prepush-heavy-lint-no-build-hosts branch October 30, 2026 00:49
@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 1e255ee8 Deployed Oct 30, 2026 by teamleaderleo via cmux-tui tree preflight #79195
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant