fix: keep non-ASCII text readable in merged JSON settings files - #4773
Conversation
The Copilot .vscode/settings.json merge and the events writer that merges hooks into agent configs (.claude/settings.json, opencode.json, .cursor/hooks.json, ...) called json.dumps without ensure_ascii=False, so every non-ASCII character already in the user's file was rewritten as a \uXXXX escape. Both files are user-owned and hand-edited. Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Both writers can now fail on accepted JSON containing escaped lone surrogates.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Preserves readable non-ASCII text when merging user-owned JSON settings.
Changes:
- Disables ASCII escaping in both JSON writers.
- Adds regression coverage for Copilot and event configuration merges.
| File | Description |
|---|---|
src/specify_cli/integrations/copilot/__init__.py |
Preserves Unicode in VS Code settings. |
src/specify_cli/events/__init__.py |
Preserves Unicode in event configurations. |
tests/integrations/test_integration_copilot.py |
Tests readable Unicode in VS Code settings. |
tests/specify_cli/events/test_events.py |
Tests readable Unicode in Claude settings. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
json.loads accepts an escaped lone surrogate such as "\ud800", and with ensure_ascii=False the strict UTF-8 write then raised UnicodeEncodeError on a config the old writer could round-trip. errors="backslashreplace" writes that character back as its \uXXXX escape and leaves the rest of the text readable. Assisted-by: Claude Code (model: Claude Opus 5.5, autonomous)
|
Addressed the Copilot review in 4d6eec2. Both writers now pass Drafted on behalf of @kartsan03 by Claude Code (model: Claude Opus 5.5, autonomous); code, tests and this comment AI-drafted. |
|
Please address Copilot feedback |
|
@mnriem Both Copilot findings are addressed in 4d6eec2 (summary above), and the two threads are now outdated. Could you re-run the Copilot review when you get a chance? Drafted on behalf of @kartsan03 by Claude Code (model: Claude Opus 5.5, autonomous); comment AI-drafted. |
|
Thank you! |

Description
Two writers that merge Spec Kit entries into a user's existing JSON config call
json.dumpswithoutensure_ascii=False, so every non-ASCII character already in that file comes back as a\uXXXXescape:CopilotIntegration._merge_vscode_settings→.vscode/settings.json(Copilot--commandsmode)events._safe_write_json→ the agent hook configs merged byinstall_integration_events/remove_integration_events(.claude/settings.json,opencode.json,.cursor/hooks.json,.devin/hooks.v1.json, …)Both files are user-owned and hand-edited (
_load_user_jsonalready treats them that way), so runningspecify initor toggling events mangles text the user wrote. This is the JSON counterpart of #4148, which fixed the same thing for YAML overlay files.Reproduction on current
main, with an existing.vscode/settings.json:{ "cSpell.words": ["naïve", "Привіт"] }specify init --here --integration copilot --integration-options="--commands" --forcerewrites it as:{ "cSpell.words": [ "naïve", "Привіт" ],Same for
.claude/settings.json(e.g. anenvvalue) when Claude hooks are installed.The value still parses back identically, so this is not data loss, only legibility of a file the user owns. Both files are already written with
encoding="utf-8"; pure-ASCII files produce identical bytes.Testing
Tested locally with
uv run specify --helpRan existing tests with
uv sync && uv run pytestTested with a sample project (if applicable)
New tests, one per writer:
test_setup_merge_keeps_non_ascii_vscode_settings_readable(Copilot) andtest_merge_keeps_non_ascii_user_settings_readable(Claude hooks merge). Both fail onmainand pass with the change; each also checks the value round-trips throughjson.loads.Full suite: 8399 passed, 212 skipped (Linux, Python 3.13)
uvx ruff@0.15.0 check src tests: cleanSample project: the
specify initcommand above, run againstmainand this branch (output shown above).AI Disclosure
AI disclosure: Claude Code (Claude Opus 5.5, xhigh reasoning effort, autonomous agent mode) was used for drafting/refactoring the code change, the regression tests and this description.