Skip to content

fix(dispatch): raise max to 2 and add schema static gate (#1772) - #1777

Merged
bradygaster merged 1 commit into
devfrom
bradygaster-eecom-fix-dispatch-probe-gate-1772
Aug 20, 2026
Merged

fix(dispatch): raise max to 2 and add schema static gate (#1772)#1777
bradygaster merged 1 commit into
devfrom
bradygaster-eecom-fix-dispatch-probe-gate-1772

Conversation

@bradygaster

Copy link
Copy Markdown
Owner

Problem

The merge-continuation relay silently fails. The implement worker emits an empty dispatch_workflow probe first, which consumes the only slot (max: 1). The real dispatch is silently discarded. Squad fires with no inputs and creates junk issues.

Confirmed three times in bradygaster/aspiregregator-squad-e2e:

Prior fix #1766 added prompt wording ("NEVER call dispatch_workflow with empty/placeholder arguments"). The wording was present in the live runs and the probe happened anyway. Prompt text is not a reliable gate for LLM runtime behavior.

Fix

1. workflows/squad-implement-worker.md — raise dispatch-workflow: max from 1 to 2

With max: 2, the real dispatch gets a second slot even when the LLM probes first. The probe fires too (dispatching squad with no inputs), but the real dispatch now succeeds — the relay is no longer silently broken.

2. scripts/check-workflow-input-interpolation.mjs — dispatch schema static gate

Added checkDispatchWorkflowSchemas() that validates every JSON code block adjacent to a dispatch_workflow reference. Catches:

  • Missing/empty workflow_name — confirmed failure shape in run 32316227601
  • Missing/empty inputs object — top-level command/issue_number are silently dropped by gh-aw
  • inputs present but missing issue_number
  • Top-level command/issue_number keys

The gate runs in CI at .github/workflows/squad-workflow-lint.yml:146 on every PR that touches workflows.

Tests Added (6 new)

Test Broken state Fixed state
worker dispatch-workflow max is at least 2 ❌ FAILS (max:1) ✅ passes
Gate fails: missing workflow_name ❌ FAILS (gate would miss it) ✅ gate catches it
Gate fails: top-level command instead of inputs ❌ FAILS ✅ catches
Gate fails: missing inputs.issue_number ❌ FAILS ✅ catches
Gate passes: well-formed schema ✅ passes ✅ passes
Gate passes: current workflow files ✅ passes ✅ passes

The max >= 2 test is the key structural test that the wording-only approach (#1766) lacked: prompt text can be present and the live run still broken; this test directly asserts the frontmatter value that governs runtime behavior. It fails against the old max: 1 state and passes after the fix.

What This Fix Does NOT Cover

The LLM probe behavior is not prevented — that requires gh-aw to validate dispatch payloads before counting them toward max. The fix reduces the blast radius and catches authoring mistakes.

Coordination Required — squad.md empty-command guard

When workflow_dispatch fires with no command and no issue_number (from the probe), squad runs in an undefined mode and creates junk issues because both inputs are required: false. Procedures should add an explicit guard at the top of squad.md that detects empty command/issue_number on workflow_dispatch, logs a visible failure comment, and stops. This is a separate PR and should not block this fix.

Supersedes failed prompt-only approach in #1766.

Closes #1772

- workflows/squad-implement-worker.md: raise dispatch-workflow max from 1 to 2
  so the real dispatch survives an LLM probe. With max:1, the empty probe consumed
  the only slot and the real payload was silently discarded (confirmed 3 live runs
  in aspiregregator-squad-e2e). Raising to 2 gives the real dispatch a second slot.

- scripts/check-workflow-input-interpolation.mjs: extend static gate with
  checkDispatchWorkflowSchemas() that validates every JSON code block adjacent to a
  dispatch_workflow reference. Catches: missing workflow_name (failure shape 3 from
  run 32316227601), missing inputs object (top-level keys are silently dropped by
  gh-aw), missing inputs.issue_number, and top-level command/issue_number.
  Supports SQUAD_GATE_SCAN_OVERRIDE env var for fixture-based testing.

- test/gh-aw-quality.test.ts: add 6 tests
  - 'worker dispatch-workflow max is at least 2': fails against old max:1 state,
    passes after fix. This is the structural test the prompt-wording approach lacked.
  - 4 gate failure tests: missing workflow_name, top-level inputs, missing
    issue_number, valid schema passes.
  - Regression guard: gate passes against current workflow files.

Closes #1772. Supersedes failed prompt-only approach in #1766.

Coordination needed: Procedures to add squad.md empty-command guard (separate PR) —
when workflow_dispatch fires with no command/issue_number, squad should detect and
report failure immediately rather than running in undefined mode.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 20, 2026 19:55
@github-actions

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #1777

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 4
Files added 0
Files modified 4
Files deleted 0
Modules touched 4

🎯 Risk Factors

  • 4 files changed (≤5 → LOW)
  • 4 modules touched (2-4 → MEDIUM)

📦 Modules Affected

root (1 file)
  • workflows/squad-implement-worker.md
scripts (1 file)
  • scripts/check-workflow-input-interpolation.mjs
squad-state (1 file)
  • .squad/agents/eecom/history.md
tests (1 file)
  • test/gh-aw-quality.test.ts

This report is generated automatically for every PR. See #733 for details.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit 4652526

PR Scope: 🔧 Infrastructure

⚠️ 2 item(s) to address before review

Status Check Details
Single commit 1 commit — clean history
Not in draft Ready for review
Branch up to date Up to date with dev
Copilot review No Copilot review yet — it may still be processing
Changeset present No source files changed — changeset not required
Scope clean ⚠️ PR includes 1 .squad/ file(s) — ensure these are intentional
No merge conflicts No merge conflicts
Copilot threads resolved 1 unresolved Copilot thread(s) — fix and resolve before merging
CI passing All checks passing

Files Changed (4 files, +386 −7)

File +/−
.squad/agents/eecom/history.md +10 −0
scripts/check-workflow-input-interpolation.mjs +164 −6
test/gh-aw-quality.test.ts +211 −0
workflows/squad-implement-worker.md +1 −1

Total: +386 −7


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Squad File Leakage Detected

The following .squad/ files were modified in this PR:

  • .squad/agents/eecom/history.md

These files affect team routing, agent charters, and decisions.
If intentional, ensure approval from the team lead.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the merge-continuation relay by preventing dispatch_workflow schema probes from silently consuming the only safe-output slot, and adds a CI/static validation gate to catch malformed dispatch_workflow JSON examples in workflow prompts.

Changes:

  • Raise dispatch-workflow.max from 1 → 2 in workflows/squad-implement-worker.md so a probe can’t discard the real dispatch payload.
  • Add a static gate in scripts/check-workflow-input-interpolation.mjs to validate dispatch_workflow JSON example blocks adjacent to dispatch_workflow references.
  • Add/extend Vitest coverage to structurally assert the max >= 2 contract and validate the new schema gate behavior (including fixture-based failure cases).
Show a summary per file
File Description
workflows/squad-implement-worker.md Raises dispatch-workflow.max to 2 to avoid first-wins probe suppression.
test/gh-aw-quality.test.ts Adds structural test for max >= 2 and a suite covering the new dispatch schema gate.
scripts/check-workflow-input-interpolation.mjs Implements checkDispatchWorkflowSchemas() and adds scan override support for fixtures.
.squad/agents/eecom/history.md Records the investigation summary and fix details for #1772.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +1134 to +1152
function runGate(scanDir: string): { exitCode: number; stderr: string; stdout: string } {
// The script resolves SCAN_DIRS relative to repo root via import.meta.url.
// We invoke it with a patched environment variable so the fixture directory is
// scanned instead of the real workflows directory.
const result = spawnSync(
process.execPath,
[scriptPath],
{
env: { ...process.env, SQUAD_GATE_SCAN_OVERRIDE: scanDir },
encoding: 'utf8',
cwd: process.cwd(),
}
);
return {
exitCode: result.status ?? 1,
stderr: result.stderr ?? '',
stdout: result.stdout ?? '',
};
}
@bradygaster
bradygaster merged commit 355b860 into dev Aug 20, 2026
17 checks passed
bradygaster added a commit that referenced this pull request Aug 20, 2026
… (squad.md) (#1778)

* Fix long-path planning lifecycle defects in squad.md

Working as Procedures (Prompt Engineer)

Repairs three defects in workflows/squad.md that gate the long-path
end-to-end scenario, all grounded in readable sources of truth rather
than inference:

- #1758.1: squad-plan-accept Step 1 rewritten as "Find Plan and Route"
  so program/implementation artifacts route to Accept Scope -> Accept
  Implementation -> Activate before the "No plan found" hard-fail. The
  routing note is no longer dead code.
- #1758.2: Implement descends the sub-issue hierarchy recursively and
  dispatches only leaf tasks; intermediate epics are never handed to a
  worker. Preserves the 3-slot dispatch cap.
- #1758.3: next-hints re-synced to the authoritative ontology
  (shared/squad-planning-ontology.md) so validate precedes BOTH accept
  steps: program -> implementation -> validate -> accept scope ->
  accept implementation -> activate.
- #1759: Owner/Agent fields bind to the team.md Name column (never a
  Role string) at every emission site; squad:{owner} labels mint from
  the lowercased cast Name, forbidding squad:lead.
- #1756: research artifact's >=200-char floor replaced with a structural
  contract (Evidence table, Goals, Non-goals, Load-bearing assumptions,
  Open decisions, Acceptance framing + Rn IDs + one citation token per
  evidence row), enforced by the MANDATORY verify step. Structural half
  only; the taste-judgment half (#1757) stays deferred.

Adds test/gh-aw-plan-lifecycle.test.ts (18 assertions), including a
role-string-leak detector that catches a Role string reaching an Owner
column. No packages/*/src touched, so no changeset required.

Closes #1758
Closes #1759
Closes #1756

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Guard empty workflow_dispatch probe against junk-issue creation

Working as Procedures (Prompt Engineer)

Defense-in-depth paired with EECOM's dispatch-workflow max fix (PR #1777).
The worker relay fires an empty workflow_dispatch probe before its real
dispatch; PR #1777 raises the worker's dispatch-workflow max so the real
dispatch survives, but the probe still arrives at squad.md with empty
inputs. squad.md previously handled empty inputs by CREATING an issue
(titled "Squad workflow dispatch missing command/issue_number") — that is
the exact junk-issue generator behind fixture issues #12 and #14, which
destroys signal during the E2E series.

Replace both issue-minting paths with a MANDATORY activation guard at the
top of the workflow_dispatch path:
- Empty/missing command on workflow_dispatch: emit a single
  ::warning:: log annotation and STOP — no issue, no comment, no skill.
- Non-empty command naming an issue-bound mode but no issue_number:
  emit a ::warning:: annotation and STOP — no issue.

The guard makes the surviving probe harmless and visible (a run-log
annotation) instead of silently minting junk. It also holds if the LLM
ever emits a third dispatch entry, which would defeat the max bump alone.

Adds guard assertions to gh-aw-plan-lifecycle.test.ts and updates the
gh-aw-quality issue_number contract test to expect the guarded halt
instead of the removed issue-creation behavior. No packages/*/src touched.

Refs #1772, PR #1777

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: brady gaster <bradygaster@github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
bradygaster pushed a commit that referenced this pull request Aug 23, 2026
FIDO's BLOCKING review on #1832: the /squad parser hardening was tested in
isolation and broke its callers. workflow_dispatch delivers a BARE command --
squad-implement-worker.md relays {"command": "implement"}, and squad.md's own
input schema documents cast/implement/connect org/repo, none carrying a /squad
prefix. PC-1 returned NO_COMMAND for every one of those, routing a structurally
valid run into PC-3 and hard-failing it. Manual dispatch and the autonomous
implement relay worked before the hardening and did not after.

Add Step PC-0, which normalizes the dispatch input before PC-1 sees it:
trim, leave an existing /squad prefix alone (idempotent), otherwise prepend
one, and emit EMPTY_DISPATCH for empty input so the activation guard halts it
silently instead of PC-3 commenting and failing (PR #1777, junk issues #12/#14).

PC-1 is deliberately NOT loosened. The paths are asymmetric: on the comment
path a missing token IS the error condition, so loosening would reopen #1824.
A regression test guards that wrong fix; mutation proves the guard fires.

Also resolve the text contradiction that made the parser nondeterministic --
one passage said a dispatched value skips parsing, another marked PC-1
MANDATORY over it. Choosing a source now never skips parsing.

Fix the greedy sub(/^.*\/squad/,"") that stripped through the LAST token on a
line, resolving "/squad cast, then /squad status" to `status` -- a different
mode than requested, silently. Use match() with substr($0, RSTART+RLENGTH).

Record PC-3's "exit non-zero" as a known limitation: it is a prompt directive,
not enforced code. Steps 1-3 are load-bearing because their output survives the
run whatever exit status the agent chooses.

Paste RETRO's shell input security contract as normative parser requirements
(env: channel, four forbidden anti-patterns, six per-hop rules). Hop 1 --
compiler-channel compliance -- is UNMEASURED here: this repo ships no compiled
gh-aw output. The verification gate is tracked in #1834 and explicitly not
built in this PR; the text says so, so a declared requirement cannot be
mistaken for an enforced one.

Tests: 15 -> 27 in gh-aw-command-parse. Three mutations each turn the suite red
AND name the offending input: PC-0 pass-through, greedy sub() restored, PC-1
loosened. All four gh-aw suites green, including strict gh aw compile and the
prompt budget guard.

Refs #1824, #1834

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
bradygaster added a commit that referenced this pull request Aug 23, 2026
* fix(gh-aw): fail loudly when /squad parses no command

`/squad cast` silently no-opped with a green check unless the command
started the issue body. `Parse Command` said "strip /squad prefix" — a
position-0 assumption — and had no failure branch, so a run that cast
nothing was byte-identical to one that worked. That is the first-run path
for every new user.

Parse Command is now three steps. PC-1 scans the whole body for the first
/squad token and prints either the argument text or the NO_COMMAND
sentinel. PC-2 routes NO_COMMAND to PC-3 and explicitly forbids falling
back to `cast`. PC-3 is mandatory: it greps the body, emits ::error:: with
the offending text quoted verbatim, comments on the issue, and exits
non-zero.

The parser is an LLM prompt, so the parse is declared as concrete shell
commands in the markdown and the test extracts and executes those exact
commands against real issue bodies — the declared contract and its
observed behavior are independent sources.

Two mutations were run against the fix. Reintroducing position-0
anchoring was caught by only 1 of 14 assertions: awk `^` anchors per
record, so a line-anchored parser still handles a command on its own line
after prose, and the headline "prose, blank line, then command" case
passed under the reintroduced bug. Indented and mid-sentence cases were
added to close that blind spot; the mutation then failed 2 tests naming
the input. Replacing PC-3's diagnostic with a generic message — leaving
every status signal intact, the shape that slipped through on #1793 — was
caught by 3 assertions, because they require the diagnostic to contain the
offending text rather than merely to exist.

The suite resolves Git Bash rather than skipping on Windows, and asserts a
shell was found, so it cannot report green for assertions that never ran.

Closes #1824

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix(gh-aw): normalize bare workflow_dispatch commands before PC-1

FIDO's BLOCKING review on #1832: the /squad parser hardening was tested in
isolation and broke its callers. workflow_dispatch delivers a BARE command --
squad-implement-worker.md relays {"command": "implement"}, and squad.md's own
input schema documents cast/implement/connect org/repo, none carrying a /squad
prefix. PC-1 returned NO_COMMAND for every one of those, routing a structurally
valid run into PC-3 and hard-failing it. Manual dispatch and the autonomous
implement relay worked before the hardening and did not after.

Add Step PC-0, which normalizes the dispatch input before PC-1 sees it:
trim, leave an existing /squad prefix alone (idempotent), otherwise prepend
one, and emit EMPTY_DISPATCH for empty input so the activation guard halts it
silently instead of PC-3 commenting and failing (PR #1777, junk issues #12/#14).

PC-1 is deliberately NOT loosened. The paths are asymmetric: on the comment
path a missing token IS the error condition, so loosening would reopen #1824.
A regression test guards that wrong fix; mutation proves the guard fires.

Also resolve the text contradiction that made the parser nondeterministic --
one passage said a dispatched value skips parsing, another marked PC-1
MANDATORY over it. Choosing a source now never skips parsing.

Fix the greedy sub(/^.*\/squad/,"") that stripped through the LAST token on a
line, resolving "/squad cast, then /squad status" to `status` -- a different
mode than requested, silently. Use match() with substr($0, RSTART+RLENGTH).

Record PC-3's "exit non-zero" as a known limitation: it is a prompt directive,
not enforced code. Steps 1-3 are load-bearing because their output survives the
run whatever exit status the agent chooses.

Paste RETRO's shell input security contract as normative parser requirements
(env: channel, four forbidden anti-patterns, six per-hop rules). Hop 1 --
compiler-channel compliance -- is UNMEASURED here: this repo ships no compiled
gh-aw output. The verification gate is tracked in #1834 and explicitly not
built in this PR; the text says so, so a declared requirement cannot be
mistaken for an enforced one.

Tests: 15 -> 27 in gh-aw-command-parse. Three mutations each turn the suite red
AND name the offending input: PC-0 pass-through, greedy sub() restored, PC-1
loosened. All four gh-aw suites green, including strict gh aw compile and the
prompt budget guard.

Refs #1824, #1834

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: brady gaster <bradygaster@github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
bradygaster added a commit that referenced this pull request Aug 23, 2026
Closes #1835

PC-0 normalized the dispatched command with an awk program gated on NR==1, so
a value whose first character is a newline put an EMPTY string on the scanned
record: PC0($'\nimplement') returned EMPTY_DISPATCH. That routes to the
activation guard, which halts with a ::warning:: and no comment by design (PR
#1777, junk issues #12/#14) -- so a structurally valid implement dispatch
would halt silently instead of running. Silent loss of a valid command is the
same defect class #1824 and #1832 exist to close.

Scan the first non-empty line instead: skip blank records, take the first one
carrying content, and fall through to EMPTY_DISPATCH via END only when every
record was empty or whitespace-only. Genuinely empty input still reaches the
silent activation-guard halt, so #1777 stays closed.

The one-liner is written without inter-statement spaces deliberately.
workflows/squad.md sits ~37 bytes above the 5 KB floor of the 100 KB prompt
budget asserted by gh-aw-quality's "reports combined bytes and headroom", so
the readable spacing and an explanatory paragraph both overran the gate. The
behavior is documented in the tests instead. That headroom is now ~65 bytes
and is a blocker for the next change to this file; it needs its own issue.

Tests: 27 -> 36 in gh-aw-command-parse. Reverting the awk to NR==1 turns four
of the new cases red, and each failure message names the offending input, so a
truncating parser cannot pass them. The whitespace-only cases ('', '   ',
'\n', '\n\n', ' \n \t \n ') pin EMPTY_DISPATCH so the fix cannot overshoot,
and the load-bearing PC-1 cases are untouched -- a bare token still fails on
the comment path.

Co-authored-by: brady gaster <bradygaster@github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bradygaster
bradygaster deleted the bradygaster-eecom-fix-dispatch-probe-gate-1772 branch September 9, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Merge-continuation dispatch destroyed by empty schema probe; #1766's prompt guard is insufficient

2 participants