Skip to content

Workflow Contracts

Dispatch sync: The scaffold dispatch.yml (at internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml) and the repo's reusable-dispatch.yml (at .github/workflows/reusable-dispatch.yml) share identical routing logic for different installation modes (per-org vs per-repo). When changing the jq payload construction, stage routing, or input/secret threading in one, apply the same change to the other. The coder app cannot push .github/workflows/ — review-handoff routing for #7384 ships as a maintainer patch; see Review handoff dedup. The GitLab scaffold no longer uses a native merge_request_event dispatch path (fullsend-dispatch.yml is a version-marker stub; see #7322). All GitLab events — including MR open, merge, and closed-unmerged — are discovered by the cron poller (fullsend-poll.yml) and dispatched as API-triggered pipelines on the protected default branch. Protected CI/CD variables are unavailable on unprotected MR refs, which is why native MR pipelines cannot run agent stages. When built-in harness triggers land (#2896-2901), poller routing can be replaced by fullsend dispatch --input-driver json.

Secret threading: GHA reusable workflows do not inherit secrets — they must be explicitly forwarded by every caller. (Repository and organization-level vars are automatically visible inside called reusable workflows and do not need forwarding.) When any reusable workflow (.github/workflows/reusable-*.yml) adds a new secrets: or inputs: (workflow_call) entry, trace both installation-mode chains and ensure every hop forwards the new entry:

  • Per-org chain (deprecated per ADR 44): scaffold thin callers (internal/scaffold/fullsend-repo/.github/workflows/<agent>.yml) → reusable workflow (.github/workflows/reusable-<agent>.yml). Note: dispatch.yml also participates in this chain for routing and event-derived inputs (forwarded via gh workflow run -f ...), though not for secrets — thin callers pull secrets from their own repo/org context. This entire chain is scheduled for removal per ADR 44.
  • Per-repo chain: shim template → reusable-dispatch.yml (stage logic for triage/code/review/fix/retro/prioritize is inlined directly as jobs per ADR 62 — there is no separate reusable-<agent>.yml hop for per-repo mode; thread new secrets/inputs into the relevant inline job). Exception: prioritize.yml is installed per-repo as a thin caller that receives workflow_dispatch from the org-level scheduler and calls reusable-prioritize.yml directly, bypassing the dispatch shim. Standalone reusable-<stage>.yml files still exist but now serve only the per-org chain (and the prioritize thin caller exception) until it is removed per ADR 44.

Silent failures and required-flag consistency: Omitting a secret that is required: true at every hop in the chain fails loudly at workflow-call validation time and self-enforces. However, a secret whose required flag is false at any upstream hop can still arrive as an empty string at a downstream required: true consumer — GitHub Actions' required-secret validation only checks key presence, not that the resolved value is non-empty. For example, FULLSEND_GCP_WIF_PROVIDER is required: false in reusable-dispatch.yml but required: true in every downstream reusable-<stage>.yml, so an installer that never sets it satisfies the key-presence check while the actual value is empty. Treat a missing forwarding hop the same as a missing sync — it is a correctness bug, not a cosmetic issue. Required-flag consistency across the whole chain matters, not just the flag at the final consumer.

Security — consuming threaded inputs: When a newly-threaded entry carries user- or event-controlled data, consume it via env: in the final run: step — never interpolate ${{ ... }} directly into a shell block (see the Security note atop reusable-dispatch.yml). This prevents the GHA script-injection class of bugs the project defends against elsewhere.

When reviewing PRs: If a diff adds or renames a secrets: or inputs: entry in a reusable workflow, check that all callers in both chains have been updated. Flag a missing forwarding hop as a medium-severity or higher finding. New secrets/inputs must be forwarded only to the hop(s)/stage(s) that need them — do not use secrets: inherit as a substitute for explicit forwarding (OTEL_EXPORTER_OTLP_TRACES_HEADERS, for example, is explicitly forwarded to every inline stage job in reusable-dispatch.yml because each stage runs an agent that emits traces). workflow_call_alignment_test.go already automates much of this verification — see TestWorkflowCallInputAlignment (validates required inputs/secrets are threaded through both chains) and TestOTELHeadersSecretThreading (bespoke test for optional secrets). For new optional secrets/inputs, extend those tests or add a similar one rather than relying solely on manual tracing.