Summary
batch:@file.json (and the ops wrapper form) breaks edit/replace/replace_lines sub-ops whose old/new value contains a literal :::. The same edit works fine as a standalone edit:@- call.
The @file JSON route is supposed to bypass ::: tokenization (fields are explicit: old, new, path). Standalone edit:@- honors that. But when the edit runs inside batch, the sub-op appears to be re-serialized to the colon-CLI form (edit:::OLD:::NEW:::PATH) and re-parsed — so a ::: inside the content collides with the field separator.
Repro
Payload (.max/ops.json):
{
"continue_on_error": false,
"ops": [
{ "op": "edit", "path": ".supertool.json",
"old": "\"example\": \"read:OLD.php:::grep=class\"",
"new": "\"example\": \"read:src/Foo.php:::grep=class\"" }
]
}
./supertool 'batch:@.max/ops.json'
Actual
--- edit::: "example": "read:...:::grep=class"::: "example": "read:src/Foo.php:::grep=class":::.supertool.json ---
ERROR: file not found: "example": "read:src/Foo.php
Everything after the first ::: in new is treated as PATH.
Expected
Edit applies. The JSON old/new/path fields are explicit and should never be re-tokenized — exactly as standalone edit:@- behaves.
Workaround
Run any edit whose content contains ::: as an individual edit:@- stdin call instead of inside batch.
Likely location
batch handler in supertool.py — where it dispatches each ops[] entry to the underlying op. It should pass the structured fields straight through to the op implementation rather than rebuilding a colon-CLI string. Standalone edit:@file already does this correctly; batch should reuse that path.
Impact
Silent-ish failure mid-batch: with continue_on_error: false the batch halts at the first :::-containing edit, leaving earlier edits applied and later ones skipped — easy to mistake for a partial success. Hit this compacting .supertool.json (shortening read:PATH:::grep= examples).
Summary
batch:@file.json(and theopswrapper form) breaksedit/replace/replace_linessub-ops whoseold/newvalue contains a literal:::. The same edit works fine as a standaloneedit:@-call.The
@fileJSON route is supposed to bypass:::tokenization (fields are explicit:old,new,path). Standaloneedit:@-honors that. But when the edit runs inside batch, the sub-op appears to be re-serialized to the colon-CLI form (edit:::OLD:::NEW:::PATH) and re-parsed — so a:::inside the content collides with the field separator.Repro
Payload (
.max/ops.json):{ "continue_on_error": false, "ops": [ { "op": "edit", "path": ".supertool.json", "old": "\"example\": \"read:OLD.php:::grep=class\"", "new": "\"example\": \"read:src/Foo.php:::grep=class\"" } ] }Actual
Everything after the first
:::innewis treated asPATH.Expected
Edit applies. The JSON
old/new/pathfields are explicit and should never be re-tokenized — exactly as standaloneedit:@-behaves.Workaround
Run any edit whose content contains
:::as an individualedit:@-stdin call instead of insidebatch.Likely location
batch handler in
supertool.py— where it dispatches eachops[]entry to the underlying op. It should pass the structured fields straight through to the op implementation rather than rebuilding a colon-CLI string. Standaloneedit:@filealready does this correctly; batch should reuse that path.Impact
Silent-ish failure mid-batch: with
continue_on_error: falsethe batch halts at the first:::-containing edit, leaving earlier edits applied and later ones skipped — easy to mistake for a partial success. Hit this compacting.supertool.json(shorteningread:PATH:::grep=examples).