| id | FIX-385 |
|---|---|
| title | template-init followed by render silently corrupts round-trip JSON via double-quoting auto-escape |
| status | complete |
| branch | fix/385-template-init-json-round-trip |
| worktree | ../sc-compose-worktrees/fix/385-template-init-json-round-trip |
| target | develop |
crates/sc-compose/src/commands/template_init.rs performs literal substring
replacement on a concrete *.json file, preserving the value's original
surrounding quote characters verbatim (e.g.
"worktree_path": "{{ worktree_path }}").
crates/sc-composer/src/renderer.rs's legacy_auto_escape_callback applies
AutoEscape::Json to any template whose stripped name ends in .json, which
unconditionally wraps every substituted string value in its own quotes.
Running template-init on a JSON file with a string value, then rendering
the generated template with the same value, does not round-trip:
template-init output: "worktree_path": "{{ worktree_path }}"
render output: "worktree_path": "\"/tmp/wt\""
This is invalid, double-quoted JSON, produced with exit 0 and zero diagnostics.
In template_init.rs, when the target file's stripped extension is json,
detect and consume the surrounding quote characters as part of the
replacement span so the generated token is bare
("worktree_path": {{ worktree_path }}), matching the auto-escape contract
already exercised by renderer.rs's existing unit tests. Do not change
renderer.rs's auto-escape behavior — it is correct for the bare-placeholder
contract; template_init.rs is the side with the mismatched assumption.
crates/sc-composer/src/renderer.rs::renderer_json_auto_escape_does_not_double_quote_a_pre_quoted_string_placeholder(already added as a RED regression test) goes GREEN without modification to its assertions.- Add a
template_init.rsunit or CLI test asserting that runningtemplate-initon a concrete*.jsonfile containing a quoted string value, thenrendering the generated template with the same value, reproduces the original document byte-for-byte. cargo test --workspace,cargo fmt --all --check,cargo clippy --all-targets --all-features -- -D warnings: PASS.
crates/sc-compose/src/cli/schema.rs/pass_input.rsclap-error-vs---jsonbypass (issue #386, FUZZ-002/FUZZ-003) — separate root cause, separate branch.- Any change to the JSON auto-escape contract for hand-written (non-generated) templates.
- Round-trip regression test passes; existing renderer auto-escape tests are unaffected.
cargo test --workspace,cargo fmt --all --check,cargo clippy --all-targets --all-features -- -D warnings: PASS.- Closeout Evidence records the fix commit(s).
- Planning index gate:
docs/project-plan.mdincludes the sprint entry before closeout.
- Issue #385: #385
- Fuzz finding FUZZ-001, campaign report
site/reports/20260811-3-fuzz-report.json docs/requirements.mdFR-8a
Fuzz-discovered production bug; dispatched immediately, comp assigned as the harder of the two 2026-08-11 fuzz-campaign fixes.
- Status: complete.
- Implementation:
31b3ef2(fix: preserve JSON string round trips from template init) consumes the surrounding quote characters when replacing string values in JSON targets, including.json,.json.j2,.json.jinja2, and.json.jinjanames. The renderer's JSON auto-escape behavior remains unchanged and continues to own quoting for the resulting bare placeholder. - Regression coverage:
template_init_json_round_trips_string_values_through_renderverifies that template-init followed by JSON rendering reproduces the concrete document byte-for-byte; the existing renderer contract test remains green with the bare-placeholder form. - Validation: focused template-init and renderer regressions,
cargo fmt --all --check, andgit diff --checkpass. Full workspace test and clippy validation were run after the docs closeout commit.