fix(step): only auto-batch when rendered command exceeds ARG_MAX#901
fix(step): only auto-batch when rendered command exceeds ARG_MAX#901
Conversation
Auto-batching previously split jobs based on the size of the file-list
expansion alone — even when the run command never interpolated
`{{files}}`. On Windows (ARG_MAX falls back to 128KB), a hook step like
`check = "echo static-message"` running on a large repo (~20K files)
would be split into ~29 jobs and the message would print 29 times.
Move auto-batching out of `build_step_jobs` and into a new
`auto_batch_jobs` invoked from execution time, where the full tera
context is available. The new implementation renders the actual run
command for each job and only splits jobs whose rendered command would
exceed the safe ARG_MAX limit, falling back to the previous byte
estimation if rendering fails.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
There was a problem hiding this comment.
Code Review
This pull request refines the auto-batching logic for shell commands by calculating the actual size of the rendered command rather than relying solely on file list estimation. This change prevents unnecessary job splitting for commands that do not interpolate {{files}}. The batching logic is moved to the execution phase to utilize the full template context. A review comment suggests returning a zero size for empty commands to avoid triggering the fallback estimation logic unnecessarily.
| if run.trim().is_empty() { | ||
| return None; | ||
| } |
There was a problem hiding this comment.
Returning None for empty or whitespace-only commands triggers the fallback to estimate_files_string_size. If the file list is large, this will cause the job to be unnecessarily batched, which is exactly what this PR aims to avoid for commands that don't reference {{files}}. Returning Some(0) would correctly indicate that the command fits within the limit.
| if run.trim().is_empty() { | |
| return None; | |
| } | |
| if run.trim().is_empty() { | |
| return Some(0); | |
| } |
Greptile SummaryThis PR fixes a bug where steps with static run commands (no Confidence Score: 5/5Safe to merge — the fix is well-targeted, the fallback preserves previous safety-net behavior, and a regression test covers the reported bug. No P0 or P1 issues found. The render-based sizing strategy is sound, the binary search logic is correct (including edge cases where even 1 file exceeds the limit), and previously dropped fields (check_first, workspace_indicator, skip_reason) are now correctly preserved on split jobs. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant E as execution.rs
participant JB as job_builder.rs
participant B as batching.rs
participant T as tera
E->>JB: build_step_jobs(files, run_type)
JB-->>E: jobs (workspace/batch split, check_first set)
E->>B: auto_batch_jobs(jobs, base_tctx)
loop for each job
B->>T: render_run_command_size(job, files, tctx)
alt rendering succeeds
T-->>B: rendered_len
else rendering fails
B->>B: estimate_files_string_size(files) fallback
end
alt rendered_len within safe_limit
B-->>B: keep job unchanged
else rendered_len exceeds safe_limit
B->>B: binary search optimal batch_size
B-->>B: emit sub-jobs preserving check_first and workspace_indicator
end
end
B-->>E: batched jobs
E->>E: assign semaphore to first job
Reviews (1): Last reviewed commit: "fix(step): only auto-batch when rendered..." | Re-trigger Greptile |
### 🚀 Features - **(builtins)** add `buildifier` format and lint built-ins by [@plx](https://github.com/plx) in [#896](#896) ### 🐛 Bug Fixes - **(step)** only auto-batch when rendered command exceeds ARG_MAX by [@jdx](https://github.com/jdx) in [#901](#901) ### 📚 Documentation - thank Namespace for GitHub Actions runner support by [@jdx](https://github.com/jdx) in [#895](#895) ### 🔍 Other Changes - **(ci)** use !cancelled() instead of always() for final job by [@jdx](https://github.com/jdx) in [#906](#906) - **(docs)** remove shrill.en.dev analytics script by [@jdx](https://github.com/jdx) in [#903](#903) - remove rust-cache from release jobs by [@jdx](https://github.com/jdx) in [#893](#893) - invert CLAUDE.md/AGENTS.md so AGENTS.md is canonical by [@jdx](https://github.com/jdx) in [#905](#905) - set dev profile debug to 1 by [@jdx](https://github.com/jdx) in [#907](#907) ### 📦️ Dependency Updates - update anthropics/claude-code-action digest to fefa07e by [@renovate[bot]](https://github.com/renovate[bot]) in [#897](#897) - update jdx/mise-action digest to 1648a78 by [@renovate[bot]](https://github.com/renovate[bot]) in [#898](#898) - update apple-actions/import-codesign-certs action to v7 by [@renovate[bot]](https://github.com/renovate[bot]) in [#900](#900) - update autofix-ci/action action to v1.3.4 by [@renovate[bot]](https://github.com/renovate[bot]) in [#899](#899) - lock file maintenance by [@renovate[bot]](https://github.com/renovate[bot]) in [#908](#908) ### New Contributors - @plx made their first contribution in [#896](#896) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk release bookkeeping: primarily version string updates across manifests and docs with no functional code changes in this diff. > > **Overview** > Updates the project for the `v1.45.0` release by bumping the crate/CLI version (`Cargo.toml`, `Cargo.lock`, `hk.usage.kdl`, generated CLI docs) and adding the `1.45.0` entry to `CHANGELOG.md`. > > Refreshes documentation and example configs to reference the new versioned Pkl package URLs (`docs/*.md`, `docs/public/*.pkl`, `hk-example.pkl`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit cfe2da5. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: mise-en-dev <[email protected]>
Summary
Auto-batching previously split jobs based on the size of the file-list expansion alone, even when the run command never interpolated
{{files}}. On Windows whereARG_MAXfalls back to 128KB, a hook step like:…running on a large repo (~20K files) would be split into ~29 jobs and the message would print 29 times. The check command doesn't reference
{{files}}at all, so there's no reason to batch.What changed
build_step_jobsand into a newStep::auto_batch_jobsinvoked from execution time, where the full tera context is available.auto_batch_jobsalso now preservescheck_firstandworkspace_indicatoracross split jobs (the old code dropped them with a TODO note).Test plan
test/auto_batch_large_files.batstests still pass (theecho {{files}}cases still get split).check = "echo hello world"+ 1000 long-path files runs as 1 job, not many.201 files – ... – echo ...) instead of many.🤖 Generated with Claude Code
Note
Medium Risk
Changes when and how step jobs are split, which can affect execution concurrency and command invocation for large repos; includes a fallback estimator and new regression test to reduce risk.
Overview
Auto-batching is changed to render the actual step run command (with the full tera context) and only split jobs when that rendered command would exceed the safe
ARG_MAXlimit, avoiding unnecessary batching for steps that don’t interpolate{{files}}.The batching logic is moved from job construction into execution time, adds a render-based binary search with a size-estimation fallback, and preserves per-job state (e.g.
check_firstandworkspace_indicator) when creating split jobs. A new Bats regression test ensures static commands stay single-job even with huge file sets.Reviewed by Cursor Bugbot for commit 861818b. Bugbot is set up for automated code reviews on this repo. Configure here.