Named-workflow args arrive as a string: carriers silently undefined, misreported as substantive verdicts #24
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Motivation
Named-workflow invocations silently lose their entire carrier: every field destructured from
argsbecomesundefined, and the run then fails in a misleading way. This has now cost multiple runs. Worst observed shape (this run):compiler-driven-editinvoked by name with a fully specified edit returnedBOUNCE to: specify, reason: "a hole forces a design decision"— a wrong verdict produced purely by the missing carrier (the agent saw the literal stringundefinedfor EDIT and DEFINITION SITE), dressed as a substantive design finding.implement-loophas the same failure class (documented workaround in this project's memory: hardcode the carrier into a scriptPath copy — 4 prior hits withtask_range, one with a stale-plan misglob).Problem
Both workflow scripts destructure object-form
argswith no guard againstargsarriving as a string:workflows/implement-loop.js:86—} = args || {}workflows/compiler-driven-edit.js:61—const { edit_description, def_site } = args || {}When the Workflow tool delivers args as a string (observed repeatedly for named invocations; string-form args are also the documented interface the
implement/compiler-driven-editskills tell the orchestrator to use —args: "<carrier text>"), destructuring yieldsundefinedfor every field, and neither script detects it. Downstream the failure surfaces far from the cause:implement-loopglobs for a plan withiter_id/plan_pathundefined (writing/tmp/iter-undefined/stats.json, or worse, silently picking a stale plan);compiler-driven-edithands the string "undefined" to its edit agent, which correctly refuses — but the script maps that refusal onto the straddle-rule BOUNCE (to: specify), misreporting a mechanics failure as a design hole.Two-part fix, both plugin-side:
typeof args === 'string', tryJSON.parse; if that fails (or fields are still missing), treat the string as the free-text carrier where the script's contract allows it, else fail fast.INFRA_ERROR-style verdict naming the missing fields — never proceed into globbing/agent dispatch, and never map the failure onto a substantive bounce target.Triage verification at commit
dacadba. The defect is real; one load-bearing claim in the body does not hold, which changes the fix shape.Confirmed — the unguarded destructures.
implement/workflows/implement-loop.js:78-86(} = args || {}) andimplement/workflows/compiler-driven-edit.js:61(const { edit_description, def_site } = args || {}).args || {}only substitutes for a falsy value; a non-empty string is truthy, so it passes through and every destructured field readsundefined. Notypeof argscheck or missing-carrier fail-fast exists in either file (grep -n "typeof args\|INFRA_ERROR\|carrier" implement/workflows/*.jsmatches nothing). Downstream, as described: implement-loop dispatches the plan-index agent with "Read the plan at undefined" and stops only when that agent reports zero tasks (implement-loop.js:353—NEEDS_CONTEXT: no tasks found in undefined, a stop, but labelled as a plan-content problem and only after a preflight plus one agent call); compiler-driven-edit interpolatesEDIT: undefined/DEFINITION SITE: undefinedinto its edit-agent prompt and, when that agent reports a hole without a clear bounce target, defaults the bounce tospecify(compiler-driven-edit.js:139). No path in either file distinguishes a missing carrier from a substantive verdict.Refuted — "string-form args are also the documented interface". The only invocation examples in the plugin are object-form (
implement/SKILL.md:167and:178:Workflow({ name: "implement-loop", args: { mode: …, iter_id: …, plan_path: … }})); the compiler-driven arm section carries no invocation example at all, and no skill text prescribesargs: "<carrier text>". A string carrier is a caller deviating from the documented contract, not following it.Consequently fix part 1 (parse-or-passthrough, treating the string as a free-text carrier "where the script's contract allows it") has no basis — no script contract allows a free-text carrier. Fix part 2 alone suffices and is mechanical: reject a non-object
args, then fail fast on missing per-mode required fields (standard:iter_id+plan_path; mini:iter_id+red_test_path+cause_summary; compiler-driven-edit:edit_description+def_site) with a distinct infra-flavoured verdict — mirroring the existing malformed-input guards in the same file (implement-loop.js:366-374, invalidtask_range→ hard BLOCKED).The documented object-form path is unaffected; the defect fires only for a deviating caller, but then converts a mechanics failure into a misleading substantive verdict. (Path note: the body's
workflows/…paths resolve toimplement/workflows/…in this repo.)