fix(json): preserve interpolated JSON - #109
Conversation
Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: #39
This commit addresses the issue where JSON strings containing quotes and special
characters get corrupted when passed through command-stream's shell interpolation.
### Key Changes:
1. **Enhanced Shell Operator Detection** - Added detection for redirection operators
(`>`, `>>`, `<`, `2>`, etc.) in hasShellOperators to properly identify when
commands need real shell execution
2. **Improved needsRealShell Function** - Added basic redirection operators to the
unsupported features list, forcing JSON commands with redirection to use real
shell instead of virtual commands
3. **Virtual Command Bypass** - Added needsRealShell check to virtual command
decision logic to prevent JSON strings with redirection from being processed
by virtual echo command
4. **Version Bump** - Updated to 0.7.2
### Test Cases Added:
- Comprehensive JSON escaping test suite
- Examples demonstrating proper JSON usage with shell redirection
### Impact:
- ✅ JSON strings with nested quotes now work correctly with shell redirection
- ✅ Special characters (backticks, dollar signs) are properly preserved
- ✅ All existing functionality maintained (no regressions)
- ✅ Fixes configuration management, API integration, and build script use cases
Example working usage:
```javascript
const jsonData = { name: "test", description: "with 'quotes' and \"double quotes\"" };
await $`echo ${JSON.stringify(jsonData)} > config.json`; // Now works correctly!
```
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
|
We should double check more cases similar like this, make sure we have test coverage similar to all our competitors, and select behavior closer to how it would behave in sh scripts or with least surprise based on best practices from competitors. If there multiple options we should allow to configure, and use closer to sh behavior by default. |
|
🤖 AI Work Session Started Starting automated work session at 2026-09-13T14:58:59.897Z The PR has been converted to draft mode while work is in progress. This comment marks the beginning of an AI work session. Please wait for the session to finish, and provide your feedback. |
Working session summaryThe investigation found that PR 109’s original patch is incomplete, but current Evidence:
The archived CI logs are unavailable from GitHub (
Proposed design:
I do not recommend automatic JSON detection/unescaping or a new JSON-specific toggle: both would silently alter caller data. Existing quoting compatibility switches already cover the legitimate behavioral alternatives. Please approve this design and I’ll implement and finalize PR 109. This summary was automatically extracted from the AI working session output. |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost estimation:
📊 Context and tokens usage:
Total: (218.2K + 4.1M cached) input tokens, 25.1K output tokens, $5.782867 cost 🤖 Models used:
📎 Log file uploaded as Gist (2931KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
🔄 Auto-restart 1/5Reason: CI failures detected; Merge conflicts Starting new session to address the issues. Auto-restart-until-mergeable mode is active. This run will stop after 5 restart iterations in total. |
Merge current main to retain quote-aware literal interpolation and add issue-specific regression coverage.\n\nCloses #39
Working session summaryCompleted: PR #109 is ready for review, conflict-free, and cleanly mergeable.
The remaining visible failure is the stale 2025 run for the superseded SHA This summary was automatically extracted from the AI working session output. |
🔄 Auto-restart-until-mergeable Log 1/5This log file contains the complete execution trace of the AI solution draft process. 💰 Cost estimation:
📊 Context and tokens usage:
Total: (250.0K + 10.2M cached) input tokens, 39.2K output tokens, $11.322417 cost 🤖 Models used:
📎 Log file uploaded as Gist (7321KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
🎉 Auto-mergedThis pull request has been automatically merged by hive-mind.
Auto-merged by hive-mind with --auto-merge flag |
Summary
mainbranch and resolve the stale pre-monorepo conflicts.Closes #39.
Root cause and final behavior
The original branch exposed two separate problems: interpolation was quoted without regard to the surrounding author-written quote context, and virtual command dispatch could intercept commands that required real-shell redirection. The fixes subsequently merged through #100, #103, and #201 now provide the general solution on
main.This PR merges those fixes and adds the missing issue-39 regression contract.
JSON.stringify()output should be interpolated directly: it becomes exactly one literal argument. Manually added quotes or backslashes remain caller data; there is deliberately no JSON-specific unescape mode. Existing general legacy quoting switches remain available.For byte-exact file output, the documentation recommends:
echoremains suitable for the issue's parseable-JSON reproduction, but it adds a newline and has shell-dependent backslash behavior.Reproduction and regression coverage
The new test suite includes the issue's exact quoted-redirection form:
It also covers compact and formatted JSON; nested arrays and primitives; apostrophes, quotes, backslashes, control escapes, Unicode, shell metacharacters, and
printftokens; pipelines; byte-exact redirection; injection resistance; and the Keychain-shaped-w "${json}"command from the issue comments. Eighteen differential cases compare command-stream directly with/bin/shand"$JSON_VALUE".Competitor behavior
experiments/issue-39-json-competitors.mjscompares compact and adversarial formatted JSON as a single argument:/bin/shquoted variableThe checked-in experiment treats zx and Execa as optional so they do not become package dependencies.
Verification
bun test js/tests/json-escaping.test.mjs --timeout 10000— 42 passedPATH=<jq-dir>:$PATH bun test js/tests/ --timeout 10000— 1,409 passed, 6 skipped, 0 failedbun run check— ESLint, Prettier, and duplication checks passedGITHUB_BASE_REF=main bun scripts/validate-changeset.mjs— one valid patch changesetbun js/examples/json-interpolation.mjspassedbun experiments/issue-39-json-competitors.mjspassed with all optional competitors installedmain