bug: workflow_run_trigger drops inputs — missing Location header + 204 handling broken #41

Closed
opened 2026-06-21 07:02:19 +00:00 by mathias · 1 comment
Owner

Problem

workflow_run_trigger fires the workflow but silently drops all inputs. The triggered run starts with template literals unresolved — the job name shows literally Dispatch issue #${{ inputs.issue_number }} via ${{ inputs.harness }} instead of the substituted values.

The MCP tool also returns an error on every call:

{"code": -32000, "message": "missing Location header in dispatch response"}

Despite this error, the workflow does fire — but without inputs. This makes workflow_run_trigger unusable for any workflow that depends on workflow_dispatch inputs (which is the entire point of CAD dispatch).

Reproduction

gitea:workflow_run_trigger(
  owner="mathias",
  name="agentsquad",
  workflow="cad-dispatch.yml",
  ref="main",
  inputs={"issue_number": "36", "repo": "mathias/agentsquad", "harness": "agentsquad", "tier": "heavy"}
)

Expected: workflow run #N starts with inputs.issue_number=36, inputs.harness=agentsquad etc resolved in job names and steps.

Actual: workflow fires (run created), but all ${{ inputs.* }} remain as literal template strings. The inputs object was not passed to Gitea.

Verified in two consecutive calls (runs #32 and #33 in mathias/agentsquad) — both show unresolved template literals and the same missing Location header error.

Root cause (hypothesis)

The Gitea API POST /api/v1/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches returns HTTP 204 No Content on success — no Location header. The MCP tool likely expects a Location header to parse the new run ID, and when absent it both surfaces the error and fails to attach the request body (inputs) to the dispatch call.

Two possible failure modes:

  1. The inputs JSON is not being serialised into the request body at all
  2. The inputs are sent but the tool errors before confirming, and Gitea silently ignores a malformed body

Check the raw HTTP request body sent to Gitea — if inputs is absent or {}, that's the bug.

Impact

Blocks CAD dispatch from claude.ai. The entire value of workflow_run_trigger for the homelab is dispatching issue-specific agent runs with typed inputs. Without inputs landing, every dispatch is a no-op that fires the wrong workflow context.

Workaround: trigger manually from Gitea UI (Actions → cad-dispatch.yml → Run workflow).

Expected fix

  1. Serialise inputs correctly into the POST body as {"ref": "...", "inputs": {...}}
  2. Handle 204 No Content as success (no Location header expected for workflow dispatch)
  3. Return the run ID by listing recent runs after dispatch (poll GET /repos/{owner}/{repo}/actions/runs?event=workflow_dispatch&limit=1) rather than parsing a Location header

Gitea API reference

POST /api/v1/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches
Body: {"ref": "main", "inputs": {"key": "value"}}
Response: 204 No Content (success)

Acceptance criteria

  • workflow_run_trigger with non-empty inputs passes inputs to Gitea correctly
  • 204 No Content response treated as success (no error returned to caller)
  • Triggered run resolves ${{ inputs.* }} correctly in Gitea Actions UI
  • Tool returns the new run ID (via post-dispatch listing or other means)
  • missing Location header error no longer surfaced on successful dispatch

Related

  • mathias/agentsquad runs #32 and #33 — both broken dispatches
  • mathias/agentsquad issue #36 — the intended dispatch target
  • CAD dispatch workflow: mathias/agentsquad/.gitea/workflows/cad-dispatch.yml
## Problem `workflow_run_trigger` fires the workflow but silently drops all `inputs`. The triggered run starts with template literals unresolved — the job name shows literally `Dispatch issue #${{ inputs.issue_number }} via ${{ inputs.harness }}` instead of the substituted values. The MCP tool also returns an error on every call: ``` {"code": -32000, "message": "missing Location header in dispatch response"} ``` Despite this error, the workflow does fire — but without inputs. This makes `workflow_run_trigger` unusable for any workflow that depends on `workflow_dispatch` inputs (which is the entire point of CAD dispatch). ## Reproduction ``` gitea:workflow_run_trigger( owner="mathias", name="agentsquad", workflow="cad-dispatch.yml", ref="main", inputs={"issue_number": "36", "repo": "mathias/agentsquad", "harness": "agentsquad", "tier": "heavy"} ) ``` **Expected:** workflow run #N starts with `inputs.issue_number=36`, `inputs.harness=agentsquad` etc resolved in job names and steps. **Actual:** workflow fires (run created), but all `${{ inputs.* }}` remain as literal template strings. The `inputs` object was not passed to Gitea. Verified in two consecutive calls (runs #32 and #33 in `mathias/agentsquad`) — both show unresolved template literals and the same `missing Location header` error. ## Root cause (hypothesis) The Gitea API `POST /api/v1/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches` returns HTTP 204 No Content on success — no `Location` header. The MCP tool likely expects a `Location` header to parse the new run ID, and when absent it both surfaces the error and fails to attach the request body (inputs) to the dispatch call. Two possible failure modes: 1. The inputs JSON is not being serialised into the request body at all 2. The inputs are sent but the tool errors before confirming, and Gitea silently ignores a malformed body Check the raw HTTP request body sent to Gitea — if `inputs` is absent or `{}`, that's the bug. ## Impact **Blocks CAD dispatch from claude.ai.** The entire value of `workflow_run_trigger` for the homelab is dispatching issue-specific agent runs with typed inputs. Without inputs landing, every dispatch is a no-op that fires the wrong workflow context. Workaround: trigger manually from Gitea UI (Actions → cad-dispatch.yml → Run workflow). ## Expected fix 1. Serialise `inputs` correctly into the POST body as `{"ref": "...", "inputs": {...}}` 2. Handle 204 No Content as success (no Location header expected for workflow dispatch) 3. Return the run ID by listing recent runs after dispatch (poll `GET /repos/{owner}/{repo}/actions/runs?event=workflow_dispatch&limit=1`) rather than parsing a Location header ## Gitea API reference ``` POST /api/v1/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches Body: {"ref": "main", "inputs": {"key": "value"}} Response: 204 No Content (success) ``` ## Acceptance criteria - [ ] `workflow_run_trigger` with non-empty `inputs` passes inputs to Gitea correctly - [ ] 204 No Content response treated as success (no error returned to caller) - [ ] Triggered run resolves `${{ inputs.* }}` correctly in Gitea Actions UI - [ ] Tool returns the new run ID (via post-dispatch listing or other means) - [ ] `missing Location header` error no longer surfaced on successful dispatch ## Related - `mathias/agentsquad` runs #32 and #33 — both broken dispatches - `mathias/agentsquad` issue #36 — the intended dispatch target - CAD dispatch workflow: `mathias/agentsquad/.gitea/workflows/cad-dispatch.yml`
Author
Owner

Fixed in v0.3.1 (commit on main).

Root cause confirmed: Gitea's dispatch endpoint returns 204 No Content with no Location header. DispatchWorkflow required that header, so every successful dispatch errored with missing Location header and never returned a run ID. The old tests baked in a fake Location header real Gitea never sends.

Note on the "dropped inputs" hypothesis: the request body already serialized {"ref":..., "inputs":{...}} correctly — inputs do reach Gitea. The unresolved ${{ inputs.* }} you saw in runs #32/#33 is almost certainly a downstream cad-dispatch.yml / workflow-YAML issue (cf. the unquoted on:-key → YAML-boolean → 0-jobs footgun), not this tool. Worth re-verifying now that dispatch returns a real run ID to inspect.

What changed:

  • DispatchWorkflow → error-only; 204 treated as success, no Location needed. AC: 204 = success, no error surfaced.
  • Run ID resolved via listing: snapshot newest event=workflow_dispatch run before dispatch (baseline), then poll ListWorkflowRuns after and return the newest run with ID above the baseline — avoids returning a stale prior run. AC: tool returns the new run ID.
  • If the run hasn't registered within the poll window (5×1s), returns dispatched:true + a note instead of failing — the workflow is firing regardless.
  • Inputs forwarding covered by test. AC.

task check passes (lint 0, tests green -race, govulncheck clean).

Remaining AC — "triggered run resolves ${{ inputs.* }} in the Actions UI" — is a live end-to-end check against real Gitea; the tool contract is now correct. Suggest a follow-up live dispatch to confirm, and if templates are still literal, it points at cad-dispatch.yml, not gitea-mcp.

Fixed in v0.3.1 (commit on `main`). **Root cause confirmed:** Gitea's dispatch endpoint returns `204 No Content` with **no `Location` header**. `DispatchWorkflow` required that header, so every successful dispatch errored with `missing Location header` and never returned a run ID. The old tests baked in a fake `Location` header real Gitea never sends. **Note on the "dropped inputs" hypothesis:** the request body already serialized `{"ref":..., "inputs":{...}}` correctly — inputs *do* reach Gitea. The unresolved `${{ inputs.* }}` you saw in runs #32/#33 is almost certainly a downstream `cad-dispatch.yml` / workflow-YAML issue (cf. the unquoted `on:`-key → YAML-boolean → 0-jobs footgun), not this tool. Worth re-verifying now that dispatch returns a real run ID to inspect. **What changed:** - `DispatchWorkflow` → error-only; **204 treated as success**, no `Location` needed. ✅ AC: 204 = success, no error surfaced. - Run ID resolved via listing: snapshot newest `event=workflow_dispatch` run **before** dispatch (baseline), then poll `ListWorkflowRuns` after and return the newest run with ID above the baseline — avoids returning a stale prior run. ✅ AC: tool returns the new run ID. - If the run hasn't registered within the poll window (5×1s), returns `dispatched:true` + a note instead of failing — the workflow is firing regardless. - Inputs forwarding covered by test. ✅ AC. `task check` passes (lint 0, tests green `-race`, govulncheck clean). Remaining AC — "triggered run resolves `${{ inputs.* }}` in the Actions UI" — is a live end-to-end check against real Gitea; the tool contract is now correct. Suggest a follow-up live dispatch to confirm, and if templates are still literal, it points at `cad-dispatch.yml`, not gitea-mcp.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/gitea-mcp#41