fix(scripts): write --json output of the Python helpers as ASCII by kartsan03 · Pull Request #5048 · github/spec-kit · GitHub
Skip to content

fix(scripts): write --json output of the Python helpers as ASCII - #5048

Open
kartsan03 wants to merge 2 commits into
github:mainfrom
kartsan03:fix/python-scripts-utf8-stdout
Open

kartsan03 wants to merge 2 commits into
github:mainfrom
kartsan03:fix/python-scripts-utf8-stdout

Conversation

@kartsan03

Copy link
Copy Markdown
Contributor

Description

With --script py, the command templates run the Python helpers with --json and read their stdout. On Windows a captured stdout uses the ANSI code page (cp1252 on most Western setups), and _json_line / json.dumps write with ensure_ascii=False. Any character cp1252 can't encode then raises UnicodeEncodeError, the script exits 1, and the agent gets no JSON:

  • resolve_template.py constitution-template --json (/speckit.constitution) fails with the stock template, which contains →.
  • setup_tasks.py --json (/speckit.tasks) fails with the stock tasks template, which contains emoji.
  • setup_plan.py --json, check_prerequisites.py --json and setup_tasks.py --json fail for any feature whose name isn't Latin. Since [bug-fix] Fix non-latin-feature-names: preserve Unicode feature names #4780, create_new_feature.py keeps names like 001-Додати-експорт-звітів, and these scripts print that path.

#3890 and #3892 fixed the same crash in the text output of check_prerequisites.py and setup_tasks.py: the check marks fall back to [OK]/[FAIL] when stdout can't encode them. The --json paths, which the command templates use, still crash. This PR writes their JSON with ensure_ascii=True. Callers parse the JSON, so they get the same values; only the bytes on the wire are escaped. A UTF-8 stdout is unaffected.

I didn't change create_new_feature.py, which already writes UTF-8 since #4780. Forcing UTF-8 the same way in these four scripts would bypass the text-mode fallback from #3890/#3892, so I kept to the JSON encoding. I left out resolve_template.py without --json (raw template text): no shipped command calls it that way.

I haven't seen a user report of this. It came up while reading the Python helpers.

Repro on Linux, with PYTHONIOENCODING=cp1252 standing in for a captured Windows stdout. The project is from specify init <dir> --integration codex --script py, then create_new_feature.py --json "Додати експорт звітів":

Command main (505781d) This PR
setup_plan.py --json exit 1, no stdout, UnicodeEncodeError: 'charmap' codec can't encode characters in position 123-128 exit 0, JSON
check_prerequisites.py --json --paths-only exit 1, no stdout, UnicodeEncodeError exit 0, JSON
setup_tasks.py --json exit 1, no stdout, UnicodeEncodeError exit 0, JSON
resolve_template.py constitution-template --json exit 1, no stdout, UnicodeEncodeError: 'charmap' codec can't encode character '→' in position 644 exit 0, JSON

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
  • Tested with a sample project (if applicable)

New tests, each running the installed script in a subprocess with a cp1252 stdout and parsing the JSON:

  • tests/test_setup_tasks_python_parity.py: test_installed_python_json_preserves_stock_tasks_template (real specify init --script py, then feature and plan setup, then setup_tasks.py --json with the stock template).
  • tests/test_setup_plan_python_parity.py and tests/test_check_prerequisites_python_parity.py: test_python_json_preserves_non_ascii_feature_on_legacy_stdout (feature directory specs/001-功能).
  • tests/test_resolve_template_python_parity.py: test_python_json_preserves_constitution_on_legacy_stdout (an override template with Cyrillic and →).

With only the four scripts reverted to main, all four new tests fail; with the fix they pass. The existing text-mode tests from #3890/#3892 pass either way.

Full suite on Linux, Python 3.13: 12531 passed, 377 skipped (uv sync --extra test, then uv run pytest, 14m).

Ruff 0.15.0 (ruff check src tests) passes. I haven't run it on Windows itself.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

AI disclosure: Two agents, both autonomous. Codex CLI (model: gpt-6.1-sol, reasoning effort xhigh, autonomous, sandboxed and offline) found the setup_tasks.py crash while reading the Python helpers, wrote the reproduction, and generated the tests. Claude Code (model: claude-opus-5-5, autonomous) extended the fix to the other three scripts, chose the ensure_ascii approach after comparing #3890/#3892 with #4780, edited the fix and two of the tests by hand, reran the tests against main and the fix, reran the repro with the real specify CLI, ran the full suite and lint, and drafted this description. Commits carry Assisted-by: trailers.

With --script py the command templates read the helpers' JSON from a
captured stdout. On Windows that stream uses the ANSI code page, and
ensure_ascii=False made setup_plan, check_prerequisites, setup_tasks and
resolve_template --json raise UnicodeEncodeError and print nothing for
the stock constitution and tasks templates and for non-Latin feature
names. github#3890 and github#3892 fixed the text output of two of them; this covers
the JSON paths. Callers parse the JSON, so the decoded values are the
same.

Assisted-by: Codex CLI (model: gpt-6.1-sol, autonomous)
Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new setup-plan assertion expects LF-only bytes, but its fixture produces CRLF on Windows.

1 open issue
What changed in this PR

Makes Python helper JSON safe for legacy Windows stdout encodings while preserving decoded Unicode values.

Changes:

  • Enables ASCII escaping in four JSON writers.
  • Adds regression tests for Unicode paths and template content.

Validation used isolated standard-library checks; project tests were not run.

AI disclosure: GitHub Copilot (model unknown, autonomous), reviewing for the requester; review and suggested fix are AI-generated.

File Description
tests/​test_setup_tasks_python_parity.py Tests stock template JSON under cp1252.
tests/​test_setup_plan_python_parity.py Tests Unicode feature paths under cp1252.
tests/​test_resolve_template_python_parity.py Tests Unicode template JSON under cp1252.
tests/​test_check_prerequisites_python_parity.py Tests Unicode prerequisite paths under cp1252.
scripts/​python/​setup_tasks.py Escapes non-ASCII JSON characters.
scripts/​python/​setup_plan.py Escapes non-ASCII JSON characters.
scripts/​python/​resolve_template.py Escapes non-ASCII JSON characters.
scripts/​python/​check_prerequisites.py Escapes non-ASCII JSON characters.

Review effort: Max

Comment thread tests/test_setup_plan_python_parity.py Outdated
assert Path(payload["FEATURE_DIR"]) == feature
assert Path(payload["FEATURE_SPEC"]) == feature / "spec.md"
assert Path(payload["IMPL_PLAN"]) == feature / "plan.md"
assert (feature / "plan.md").read_bytes() == TEMPLATE_BODY.encode("utf-8")
…late

The fixture writes plan-template.md with write_text(), which gives CRLF
on Windows, and setup_plan copies those bytes. Comparing against the LF
constant failed on Windows even with the JSON fix in place.

Assisted-by: Claude Code (model: claude-opus-5-5, autonomous)
@kartsan03

Copy link
Copy Markdown
Contributor Author

This branch has not been deployed

No deployments
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.

3 participants