Motivation
We're using dispatch-workflow in an agentic workflow ("Silencer", in tenstorrent/tt-metal) whose entire job is to scan CI logs for noise, root-cause it, and open a small draft PR fixing the emitter. The natural next step — and the whole reason we reached for dispatch-workflow instead of just documenting "a maintainer should re-run CI" — is to have the same agent turn that opens (or amends) the fix PR also kick off a fresh workflow_dispatch run of the CI workflow it just patched, on its own PR branch, so the fix is validated automatically instead of waiting on a human to notice and manually re-run it.
This is a genuinely common shape for agentic-workflow-authored-fix patterns: agent opens/updates a branch → agent wants to validate that branch by dispatching a workflow_dispatch-triggered CI workflow against it. dispatch-workflow is presumably the intended tool for exactly this, and it's almost there — but there's no way for the agent to say which ref to run against, only which workflow.
The gap
The dispatch_workflow safe-output the agent can emit only accepts workflow_name and inputs (see DispatchWorkflowOutput in schemas/agent-output.json). There is no ref (or branch) field in that schema. The actual ref used is resolved once, server-side, in dispatch_workflow.cjs, from:
target-ref in frontmatter (a single static string, set at compile time), else
GITHUB_HEAD_REF (only populated when the dispatching workflow's own trigger event is a PR), else
GITHUB_REF / context.ref (the dispatching workflow's own triggering ref), else
- the target repo's default branch.
None of these give the agent a way to target an arbitrary branch it names at runtime — which is exactly what's needed when the branch (e.g. silencer/unused-var-layernorm) is created by the agent's own create-pull-request / push-to-pull-request-branch safe-output in that same turn, and doesn't correspond to anything in the dispatching workflow's own trigger context.
Concretely, for our scheduled Silencer workflow: it runs on schedule/workflow_dispatch/slash_command, never as a pull_request event, so GITHUB_HEAD_REF is always empty and GITHUB_REF is always refs/heads/main. target-ref can't help either — it's one fixed string in frontmatter, but Silencer's target branch is different every run (it names the branch itself, based on which noise pattern it's fixing that run). There is no configuration of the existing four resolution paths that can ever resolve to "whatever branch this agent turn just created."
We confirmed the rest of the mechanism works correctly: we built a dispatch-workflow config listing 44 target CI workflows (mostly .yaml-suffixed) and confirmed it compiles cleanly on v0.84.1 — this required the findWorkflowFile .yaml fix from #49230, which we validated by reproducing the identical compile failure on v0.84.0. The only remaining blocker is the missing per-call ref.
Proposed fix
Add an optional ref (or branch) field to the dispatch_workflow agent-output schema (DispatchWorkflowOutput in schemas/agent-output.json), and thread it through dispatch_workflow.cjs's ref resolution as the highest-priority source when present — ahead of target-ref, GITHUB_HEAD_REF, and GITHUB_REF:
let ref;
if (output.ref) {
ref = output.ref.startsWith("refs/") ? output.ref : `refs/heads/${output.ref}`;
} else if (config.target_ref) {
ref = config.target_ref;
} else if (process.env.GITHUB_HEAD_REF) {
ref = `refs/heads/${process.env.GITHUB_HEAD_REF}`;
} else if (process.env.GITHUB_REF || context.ref) {
ref = process.env.GITHUB_REF || context.ref;
} else {
ref = await getDefaultBranchRef();
}
Whether an agent-supplied ref should be constrained (e.g. to branches created by that same agent turn's own safe-outputs, to avoid an agent dispatching arbitrary refs) is a real design question worth discussing — happy to hear what shape the maintainers would want here. Even a conservative version — e.g. only trusting output.ref when it exactly matches a branch name the same turn's create-pull-request/push-to-pull-request-branch output produced — would unblock our use case while keeping the safety story tight.
Related
How we found this
Investigating this while migrating tenstorrent/tt-metal's agentic workflows to v0.84.1 and trying to wire up exactly the pattern described above for our Silencer workflow. Happy to share the exact frontmatter config we tested, or test any proposed fix against it.
Motivation
We're using
dispatch-workflowin an agentic workflow ("Silencer", intenstorrent/tt-metal) whose entire job is to scan CI logs for noise, root-cause it, and open a small draft PR fixing the emitter. The natural next step — and the whole reason we reached fordispatch-workflowinstead of just documenting "a maintainer should re-run CI" — is to have the same agent turn that opens (or amends) the fix PR also kick off a freshworkflow_dispatchrun of the CI workflow it just patched, on its own PR branch, so the fix is validated automatically instead of waiting on a human to notice and manually re-run it.This is a genuinely common shape for agentic-workflow-authored-fix patterns: agent opens/updates a branch → agent wants to validate that branch by dispatching a
workflow_dispatch-triggered CI workflow against it.dispatch-workflowis presumably the intended tool for exactly this, and it's almost there — but there's no way for the agent to say which ref to run against, only which workflow.The gap
The
dispatch_workflowsafe-output the agent can emit only acceptsworkflow_nameandinputs(seeDispatchWorkflowOutputinschemas/agent-output.json). There is noref(orbranch) field in that schema. The actual ref used is resolved once, server-side, indispatch_workflow.cjs, from:target-refin frontmatter (a single static string, set at compile time), elseGITHUB_HEAD_REF(only populated when the dispatching workflow's own trigger event is a PR), elseGITHUB_REF/context.ref(the dispatching workflow's own triggering ref), elseNone of these give the agent a way to target an arbitrary branch it names at runtime — which is exactly what's needed when the branch (e.g.
silencer/unused-var-layernorm) is created by the agent's owncreate-pull-request/push-to-pull-request-branchsafe-output in that same turn, and doesn't correspond to anything in the dispatching workflow's own trigger context.Concretely, for our scheduled
Silencerworkflow: it runs onschedule/workflow_dispatch/slash_command, never as apull_requestevent, soGITHUB_HEAD_REFis always empty andGITHUB_REFis alwaysrefs/heads/main.target-refcan't help either — it's one fixed string in frontmatter, but Silencer's target branch is different every run (it names the branch itself, based on which noise pattern it's fixing that run). There is no configuration of the existing four resolution paths that can ever resolve to "whatever branch this agent turn just created."We confirmed the rest of the mechanism works correctly: we built a
dispatch-workflowconfig listing 44 target CI workflows (mostly.yaml-suffixed) and confirmed it compiles cleanly on v0.84.1 — this required thefindWorkflowFile.yamlfix from #49230, which we validated by reproducing the identical compile failure on v0.84.0. The only remaining blocker is the missing per-call ref.Proposed fix
Add an optional
ref(orbranch) field to thedispatch_workflowagent-output schema (DispatchWorkflowOutputinschemas/agent-output.json), and thread it throughdispatch_workflow.cjs's ref resolution as the highest-priority source when present — ahead oftarget-ref,GITHUB_HEAD_REF, andGITHUB_REF:Whether an agent-supplied
refshould be constrained (e.g. to branches created by that same agent turn's own safe-outputs, to avoid an agent dispatching arbitrary refs) is a real design question worth discussing — happy to hear what shape the maintainers would want here. Even a conservative version — e.g. only trustingoutput.refwhen it exactly matches a branch name the same turn'screate-pull-request/push-to-pull-request-branchoutput produced — would unblock our use case while keeping the safety story tight.Related
.yaml/.ymlfindWorkflowFilefix, v0.84.1) — necessary but not sufficient for our use case; this issue is the remaining piece.dispatch-workflowuses caller'sGITHUB_REFfor cross-repo dispatch instead of target repo's default branch #20779 — a different symptom of the same underlying "ref resolved once, server-side, from the dispatching workflow's own context" design (there: wrong ref in a cross-repoworkflow_callrelay; here: no way to specify a same-repo branch the agent just created). Worth considering both together, since a general "let the caller specify the ref" fix would likely resolve both.How we found this
Investigating this while migrating
tenstorrent/tt-metal's agentic workflows to v0.84.1 and trying to wire up exactly the pattern described above for ourSilencerworkflow. Happy to share the exact frontmatter config we tested, or test any proposed fix against it.