Repository navigation
Conversation
…g is left to escape Without jq, setup-tasks.sh, resolve-template.sh and check-prerequisites.sh --template pass whole template content through json_escape, whose byte-by-byte loop for U+0001-U+001F takes most of their run time (about 2.1 s for setup-tasks.sh under Git Bash on Windows). When the value has no control byte left, print it directly; values that still have one take the existing loop, so the output is unchanged. Apply the same change to the copy in the github extension's resolve-tasks.sh, and add ESC, DEL and non-ASCII cases to its round-trip and core-parity test so both paths stay covered. Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues are recorded for the Bash JSON escaping optimization.
No open issues 🎉
What changed in this PR
This PR speeds up Bash JSON output when jq is unavailable by skipping byte-by-byte escaping when no control characters remain.
Changes:
- Add the same fast path to the core and GitHub extension helpers, preserving the existing control-byte fallback.
- Extend JSON round-trip and core-parity tests with ESC, DEL, and non-ASCII inputs.
| File | Description |
|---|---|
tests/extensions/github/test_github_extension.py |
Add control-byte and UTF-8 test cases. |
scripts/bash/common.sh |
Skip the escaping loop when no control bytes remain. |
extensions/github/scripts/bash/resolve-tasks.sh |
Mirror the core JSON escaping optimization. |
Review effort: Balanced (dynamic)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When
jqis not installed, the bash helpers build their--jsonoutput withjson_escapefromscripts/bash/common.sh. After the\\,",\n,\t,\r,\band\freplacements, it walks the whole value one byte at a time (substring,printf -v, compare,printf) to catch the remaining U+0001–U+001F characters. That is cheap for paths, butsetup-tasks.sh,resolve-template.shandcheck-prerequisites.sh --templatepass the whole template content through it, so for the stock tasks template the loop runs about 9,000 times on every/speckit.tasks.This change adds a fast path: if the value has no control byte left (
[[ "$s" != *[[:cntrl:]]* ]], checked under the existingLC_ALL=C), it prints the value as is and returns. Otherwise the existing loop runs unchanged.[[:cntrl:]]also matches DEL (0x7f), which takes the loop as before. The same change goes into the copy inextensions/github/scripts/bash/resolve-tasks.sh, which is kept in step with core.The output does not change. Base and patched
json_escapegive byte-identical output on every file undertemplates/andtemplates/commands/, on strings with each control byte 0x01–0x1F and 0x7F mixed with backslashes, quotes and multi-byte UTF-8, and on an empty string. The--jsonoutput ofsetup-tasks.sh,resolve-template.sh(spec and constitution templates),check-prerequisites.sh(--template checklist-templateand--paths-only) andsetup-plan.shis also byte-identical between base and patched.Numbers
Windows 11, Git for Windows bash 5.2.37 (
specify init --script sh), nojqon PATH. Two copies of the same one-feature project, one with the originalcommon.shand one with the patched one. Base and patched runs alternate, 7 runs each after a warm-up run; wall time of the whole script:setup-tasks.sh --json(/speckit.tasks)resolve-template.sh spec-template --json(/speckit.specify)resolve-template.sh constitution-template --json(/speckit.constitution)check-prerequisites.sh --json --template checklist-template(/speckit.checklist)json_escapealone on the stock tasks template (one bash process per run, so about 90 ms of bash startup is included): 1336 ms base vs 102 ms patched (medians of 7).On GitHub Actions (my fork), alternating base/patched runs, medians of 7, no
jq:setup-tasks.sh --jsonresolve-template.sh spec-template --jsonresolve-template.sh constitution-template --jsoncheck-prerequisites.sh --json --template checklist-template/bin/bash3.2,json_escapeon the tasks template/bin/bash3.2,json_escapeon the spec templateThe base and patched outputs were byte-identical on both runners for all 49 inputs, and the repo's
json_escapetests pass there.The cost grows with template size, so presets that append to a template gain more.
Testing
tests/extensions/github/test_github_extension.py: 200 passed, 5 skipped (Windows 11, Python 3.14, Git Bash). I added three cases to the existingjson_escaperound-trip and core-parity test: ESC and DEL (loop path) and non-ASCII text (fast path). They pass with and without the change.Bash helper tests (
test_setup_tasks.py, the*_python_parity.pytests for resolve-template, check-prerequisites, setup-plan and create-new-feature,test_timestamp_branches.py,test_setup_plan_feature_json.py,test_check_prerequisites_paths_only.py,tests/extensions/git, non-PowerShell cases): the same 430 pass before and after. The same 126 fail before and after on this Windows machine (Git Bash vs Windows path spellings in the parity checks), so they are unrelated to this change.Byte-for-byte comparison of base vs patched
json_escape, plus a JSON round trip of the patched output, on the inputs described above.shellcheck --severity=erroron both scripts: clean.ruff checkon the test file: clean.Tested locally with
.venv/bin/specify --help(Windows PowerShell:.\.venv\Scripts\specify.exe --help)Ran relevant existing tests with
.venv/bin/python -m pytest tests -q(Windows PowerShell:.\.venv\Scripts\python.exe -m pytest tests -q); use affected test paths for focused checks andLC_ALL=en_US.UTF-8on macOSTested with a sample project (if applicable)
AI Disclosure
AI disclosure: Opened on behalf of @b84330808. Found by profiling the bash helpers, then implemented, benchmarked and written up by Claude Code (model: claude-opus-5-5) running autonomously: code, tests, benchmarks and this description.