Skip to content

fix(triage-panel): dispatch project-sync per themed issue#971

Merged
danielmeppiel merged 1 commit intomainfrom
fix/triage-panel-dispatch-project-sync
Apr 26, 2026
Merged

fix(triage-panel): dispatch project-sync per themed issue#971
danielmeppiel merged 1 commit intomainfrom
fix/triage-panel-dispatch-project-sync

Conversation

@danielmeppiel
Copy link
Copy Markdown
Collaborator

TL;DR

The Triage Panel applies theme/* labels but the PGS project board never gets the issues: GitHub does not fire downstream issues: labeled workflows when the label change was written under GITHUB_TOKEN, which is exactly what safe-outputs.add-labels does. We had to manually backfill 46 issues into project #2304 after the first 7 sweeps. This PR wires the panel through gh-aw's safe-outputs.dispatch-workflow channel so it dispatches project-sync.yml per themed issue, restoring the trigger fan-out without introducing new secrets.

Problem (WHY)

  • Symptom observed in production. After the 7-sweep manual drain on 2026-04-26 (66 issues triaged in 1h55m), zero issues-event runs of project-sync.yml fired in the sweep window — confirmed via gh run list --workflow=project-sync.yml. Project board stayed empty for newly-themed issues.
  • Root cause is a documented GitHub Actions rule. "When you use the repository's GITHUB_TOKEN to perform tasks, events triggered by the GITHUB_TOKEN will not create a new workflow run." gh-aw's safe-outputs.add-labels writes labels via GITHUB_TOKEN by default, so project-sync.yml's on: issues: labeled filter sees nothing.
  • Affects every event-driven downstream workflow we add later. Any future automation listening for label/milestone/comment changes the panel produces would silently no-op the same way.
  • Backfill is not a fix. Running gh workflow run project-sync.yml -f content_id=... once per themed issue is a manual rescue that has to happen after every sweep, indefinitely.

Approach (WHAT)

Use gh-aw v0.68.3's safe-outputs.dispatch-workflow — same security model as add-labels (same-repo only, allowlisted workflow names, compile-time validated):

Decision Choice Rejected alternatives
How to trigger project-sync gh-aw dispatch-workflow from inside the panel PAT-routed add-labels (broadens PAT blast radius); update-project safe-output (duplicates the label→field mapping that already lives in sync_item.py); independent daily sweep workflow (extra moving piece)
When to dispatch Only when at least one theme/* label was applied this run Always (creates a no-op project-sync run on status/*-only re-triages); never (the bug stays)
Cap max: 10 max: 1 (default — too low for sweep mode); max: 50 (gh-aw ceiling — wasteful, signals nothing)
Lock-file recompile gh aw compile triage-panel and ship .lock.yml in the same commit Leave stale (CI's regeneration-drift gate would reject)

The panel knows the GraphQL node ID for every issue it processes — gh issue list --json id already returns it in the form I_kwDO..., so the only data-flow change is adding id to the existing --json field set.

Implementation (HOW)

File Change
.github/workflows/triage-panel.md Added dispatch-workflow: { workflows: [project-sync], max: 10 } to safe-outputs; extended both gh issue list --json (SCHEDULED_SWEEP) and gh issue view --json (OPT_IN_RETRIAGE / MANUAL_DISPATCH) with the id field; added a new dispatch_workflow bullet to the Output safety rails section that explains when to dispatch (only when ≥1 theme/* was applied), what node ID format to use, and why the dispatch is required.
.github/workflows/triage-panel.lock.yml Regenerated by gh aw compile triage-panel. Confirmed dispatch_workflow(max:10, workflows: [project-sync]) appears in the tool list and handler config.
CHANGELOG.md Added one ### Fixed bullet under [Unreleased] describing the symptom and the fan-out fix.

No changes to project-sync.yml — it already supports workflow_dispatch with a content_id input, which is exactly what we used during the manual backfill.

Sequence (what changes at runtime)

sequenceDiagram
  autonumber
  participant Sched as Cron 12:49 UTC
  participant Panel as Triage Panel<br/>(agent)
  participant SO as gh-aw safe-outputs<br/>handler
  participant Issues as Issues API
  participant Sync as project-sync.yml
  participant Board as PGS Project 2304
  Sched->>Panel: schedule fire
  Panel->>Issues: gh issue list --json ...,id
  Issues-->>Panel: 10 candidates incl. node IDs
  Panel-->>SO: add_labels theme/*, area/*, ...
  Panel-->>SO: dispatch_workflow project-sync<br/>{ content_id: I_kwDO... }
  SO->>Issues: PATCH labels (under GITHUB_TOKEN)
  Note over Issues,Sync: GITHUB_TOKEN label event<br/>does NOT fan out
  SO->>Sync: workflow_dispatch (under GITHUB_TOKEN)<br/>5s rate-limit between dispatches
  Sync->>Board: sync_item.py --content-id ...
  Board-->>Sync: Theme/Area/Kind/Priority/Tier set
Loading

Note

Step 7 (workflow_dispatch) IS the fix. The workflow_dispatch event type is one of the few events GitHub fans out even when triggered by GITHUB_TOKEN — that's why dispatch-workflow works where letting the natural issues: labeled event do the work does not.

Trade-offs

  • Coupling. The panel prompt now names project-sync explicitly. Acceptable: both workflows are repo-internal, both ship in this repo, and gh-aw compile-time validation rejects the workflow if project-sync.yml ever moves or loses its workflow_dispatch trigger.
  • Latency. gh-aw enforces a 5s delay between consecutive dispatches. Worst case 10 themed issues × 5s = 50s added to a sweep. Sweeps already take 16-20 min, so this is in the noise.
  • Agent-discipline dependency. The agent has to remember to dispatch when it labelled. Mitigated by the same pattern we use for milestone-consistency: an explicit MUST in the Output safety rails section, paired with a MUST NOT for the case where no theme/* was applied.
  • Per-author quota interaction. Per-author cap is 2 issues/sweep × max 10 issues × 1 dispatch each = 10 dispatches max, exactly equal to max: 10. No headroom is wasted.

Benefits

  1. Project board reflects panel triage automatically — eliminates the manual gh workflow run backfill after every sweep.
  2. Pattern reusable for any future event-driven automation we add (e.g., a milestone-changed listener) — they just wire dispatch-workflow the same way.
  3. No new secrets. No PAT scope expansion. Same security posture as the existing safe-outputs.
  4. project-sync.yml remains the sole project writer — one source of truth for the label→field mapping in scripts/project/sync_item.py.

Validation

$ gh aw compile triage-panel
✓ .github/workflows/triage-panel.md (74.9 KB)
✓ Compiled 1 workflow(s): 0 error(s), 0 warning(s)

$ grep "dispatch_workflow" .github/workflows/triage-panel.lock.yml | head -2
... Tools: ..., assign_milestone(max:12), dispatch_workflow(max:10), ...
... "dispatch_workflow":{"max":10,"workflow_files":{"project-sync":".yml"},"workflows":["project-sync"]} ...

The compiler cross-checks that project-sync.yml exists and declares workflow_dispatch — both true in main.

Manual backfill evidence (the bug this fixes)

After the 7-sweep manual drain on 2026-04-26, 46 themed issues were missing from project 2304. Running gh workflow run project-sync.yml -f content_id=<node_id> once per issue produced 47 successful runs, all of which set Theme/Area/Kind/Priority/Tier as expected. Sample log for issue #944 confirmed full field population. This PR removes the need for that manual step.

How to test

  1. Merge this PR.
  2. Apply status/needs-triage to any open issue that lacks a theme/* label (fast-path trigger).
  3. Watch the Triage Panel run; expect a verdict comment with at least one theme/* label applied.
  4. Within ~10s of the panel finishing, expect a new PGS project sync run triggered by workflow_dispatch (event column will read workflow_dispatch, not issues).
  5. Verify the issue appears in project 2304 with Theme/Area/Kind/Priority filled.

Tip

If you want to test the SCHEDULED_SWEEP path before the next 12:49 UTC fire, run gh workflow run triage-panel.lock.yml (no inputs). It will pick up to 10 untriaged issues and dispatch project-sync for each themed one.

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

GitHub does not trigger downstream 'issues: labeled' workflows from label
changes written under GITHUB_TOKEN. The Triage Panel's safe-outputs.add-labels
applies theme/* labels under GITHUB_TOKEN, so project-sync.yml's listener
silently no-op'd, leaving every panel-triaged issue off the PGS project board.

Wire the panel through gh-aw's safe-outputs.dispatch-workflow channel:
after applying any theme/* label, the agent dispatches project-sync.yml
for that issue with its GraphQL node ID as the content_id input. Cap is
max:10 (matches the per-sweep issue ceiling); gh-aw enforces a 5s delay
between consecutive dispatches so worst-case latency add is ~50s/sweep.

This preserves project-sync.yml as the sole project writer (one source of
truth for label->field mapping in scripts/project/sync_item.py) and adds
no new secrets - same security posture as the existing safe-outputs.

Validated: gh aw compile triage-panel succeeded; lock.yml shows
dispatch_workflow(max:10, workflows: [project-sync]).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 26, 2026 20:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 fixes a production gap in the Triage Panel automation where applying theme/* labels via GITHUB_TOKEN does not trigger downstream issues: labeled workflows, preventing project-sync.yml from syncing themed issues into the PGS project board. It does so by explicitly dispatching project-sync via gh-aw safe-outputs for each themed issue.

Changes:

  • Add safe-outputs.dispatch-workflow configuration to the triage panel workflow source and update issue fetch JSON fields to include GraphQL id.
  • Regenerate the compiled workflow lockfile to include the new dispatch tool and required permissions.
  • Add a changelog entry describing the fix.
Show a summary per file
File Description
CHANGELOG.md Adds an Unreleased Fixed entry describing the dispatch-based fan-out workaround.
.github/workflows/triage-panel.md Configures dispatch-workflow safe-output and documents when/how the agent should dispatch project-sync; adds id to gh issue queries.
.github/workflows/triage-panel.lock.yml Recompiled workflow including dispatch tool wiring and actions: write permission for dispatching workflows.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment thread CHANGELOG.md
### Fixed

- Fixed TLS validation failure behind corporate TLS-intercepting proxies and firewalls: `install/validation.py` now uses `requests` (honouring `REQUESTS_CA_BUNDLE`) instead of stdlib `urllib`, and surfaces a single CA-trust hint at default verbosity instead of a misleading auth error. (#911)
- Triage Panel themed issues now reach the PGS project board: the workflow dispatches `project-sync` per themed issue via the new `safe-outputs.dispatch-workflow` channel, working around GitHub's rule that `GITHUB_TOKEN`-driven label changes never fire downstream `issues: labeled` workflows. Without this, sweeps applied `theme/*` labels but the project-sync trigger silently no-op'd, leaving the board empty.
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

Changelog entries in this repo consistently end with the PR number in parentheses (e.g. "(#911)"). This new Fixed entry is missing the required "(#PR_NUMBER)" suffix, which will break the one-line-per-PR convention used throughout CHANGELOG.md.

Suggested change
- Triage Panel themed issues now reach the PGS project board: the workflow dispatches `project-sync` per themed issue via the new `safe-outputs.dispatch-workflow` channel, working around GitHub's rule that `GITHUB_TOKEN`-driven label changes never fire downstream `issues: labeled` workflows. Without this, sweeps applied `theme/*` labels but the project-sync trigger silently no-op'd, leaving the board empty.
- Triage Panel themed issues now reach the PGS project board: the workflow dispatches `project-sync` per themed issue via the new `safe-outputs.dispatch-workflow` channel, working around GitHub's rule that `GITHUB_TOKEN`-driven label changes never fire downstream `issues: labeled` workflows. Without this, sweeps applied `theme/*` labels but the project-sync trigger silently no-op'd, leaving the board empty. (#PR_NUMBER)

Copilot uses AI. Check for mistakes.
Comment on lines +459 to +474
- **`dispatch_workflow` (project-sync)**: For every issue where you
added at least one `theme/*` label in this run, you MUST also call
`dispatch_workflow` with `workflow_name: "project-sync"` and inputs
`{"content_id": "<issue node id>"}` -- where `<issue node id>` is
the `id` field returned by `gh issue list --json id` / `gh issue
view --json id` (it looks like `I_kwDO...`, NOT the integer issue
number). This triggers the PGS project board sync for that issue.
It is required because gh-aw applies `add-labels` under
`GITHUB_TOKEN`, and GitHub does NOT fire downstream workflow events
from `GITHUB_TOKEN`-driven label changes -- so without this dispatch
the issue gets the right labels but never lands on the project
board. If you did NOT add any `theme/*` label (for example a
re-triage that only touches `status/*`), do NOT dispatch -- the
project-sync workflow only acts on themed items, so the dispatch
would be a no-op. Cap is 10 dispatches per run (matches sweep
ceiling); gh-aw enforces a 5s delay between consecutive dispatches.
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

The new dispatch guidance appears to describe calling a generic dispatch_workflow tool with workflow_name + inputs, but the compiled lockfile exposes a workflow-specific safe-output tool named project_sync that only accepts a content_id argument. Please align the instructions with the actual tool name/signature generated by gh-aw (and explicitly state that the dispatched content_id must come from the same issue(s) in BATCH_ALLOW_LIST to preserve the prompt-injection safety rail).

Suggested change
- **`dispatch_workflow` (project-sync)**: For every issue where you
added at least one `theme/*` label in this run, you MUST also call
`dispatch_workflow` with `workflow_name: "project-sync"` and inputs
`{"content_id": "<issue node id>"}` -- where `<issue node id>` is
the `id` field returned by `gh issue list --json id` / `gh issue
view --json id` (it looks like `I_kwDO...`, NOT the integer issue
number). This triggers the PGS project board sync for that issue.
It is required because gh-aw applies `add-labels` under
`GITHUB_TOKEN`, and GitHub does NOT fire downstream workflow events
from `GITHUB_TOKEN`-driven label changes -- so without this dispatch
the issue gets the right labels but never lands on the project
board. If you did NOT add any `theme/*` label (for example a
re-triage that only touches `status/*`), do NOT dispatch -- the
project-sync workflow only acts on themed items, so the dispatch
would be a no-op. Cap is 10 dispatches per run (matches sweep
ceiling); gh-aw enforces a 5s delay between consecutive dispatches.
- **`project_sync`**: For every issue where you added at least one
`theme/*` label in this run, you MUST also call the dedicated
safe-output tool `project_sync` with only
`content_id: "<issue node id>"` -- where `<issue node id>` is the
`id` field for that same issue returned by `gh issue list --json id`
/ `gh issue view --json id` (it looks like `I_kwDO...`, NOT the
integer issue number). Do NOT construct `content_id` from model
output or copy it from any other issue, comment, or external source:
the dispatched `content_id` MUST come from the same issue currently
being processed, and therefore from the same issue set already
admitted by `BATCH_ALLOW_LIST`. This triggers the PGS project board
sync for that issue. It is required because gh-aw applies
`add-labels` under `GITHUB_TOKEN`, and GitHub does NOT fire
downstream workflow events from `GITHUB_TOKEN`-driven label changes
-- so without this dispatch the issue gets the right labels but
never lands on the project board. If you did NOT add any `theme/*`
label (for example a re-triage that only touches `status/*`), do
NOT dispatch -- the project-sync workflow only acts on themed
items, so the dispatch would be a no-op. Cap is 10 dispatches per
run (matches sweep ceiling); gh-aw enforces a 5s delay between
consecutive dispatches.

Copilot uses AI. Check for mistakes.
@danielmeppiel danielmeppiel merged commit 54a6cd7 into main Apr 26, 2026
19 checks passed
@danielmeppiel danielmeppiel deleted the fix/triage-panel-dispatch-project-sync branch April 26, 2026 20:22
@danielmeppiel
Copy link
Copy Markdown
Collaborator Author

Live verification: merged fix works as designed (one nuance to flag)

Verified the dispatch-workflow wiring against main (commit 54a6cd71) with two real triage runs.

Test 1 — Fast path on a new theme application: PASSED [+]

Item Value
Subject issue #841 (sergio-sisternes-epam, untriaged)
Trigger manual status/needs-triage add at 20:24:22Z
Triage Panel run 24966225229issues event, success
project-sync run 24966385694workflow_dispatch, success
INPUT_CONTENT_ID in dispatch log I_kwDOPyZEpc8AAAABAM-eRw (matches issue node ID exactly)
Board fields populated Theme=Security, Area=content-security, Kind=Bug, Priority=Low, Tier=Now (all 5)
Final issue state theme/security, area/content-security, type/bug, status/accepted, status/triaged, priority/low, good first issue, milestone 0.9.4

The sibling issues-event project-sync run (24966225228, fired by the original status/needs-triage add) skipped per the existing if: contains(... 'theme/') filter — the issue had no theme yet at that moment. Exactly as designed. Confirms the original bug: add-labels writes from gh-aw safe-outputs run under GITHUB_TOKEN and do not re-trigger downstream issues:labeled workflows; the new dispatch_workflow channel is the only path that actually puts themed issues on the board.

Test 2 — Scheduled sweep on issues with pre-existing themes: PASSED with one design nuance to surface

Manually dispatched the panel sweep at 20:37:21Z. Run 24966483357 (workflow_dispatch, success, 14m wall) triaged 5 issues:

Issue Pre-existing theme/*? Agent added new theme/*? Dispatched? Expected?
#944 yes (theme/portability since 19:50, this morning) no no yes — already on board
#913 yes (theme/portability since 04-24, by danielmeppiel) no no yes — already on board
#898 yes (theme/security since 04-24, by danielmeppiel) no no yes — already on board
#847 yes (theme/security since 04-25, by sergio) no (only added area/docs-site) no yes — already on board
#857 no no no yes — no theme, nothing to sync

Agent log explicitly records the rationale: "No project-sync dispatches — no new theme/* labels were added (all existing themes were human-applied and treated as authoritative)." The prompt's contract — "For every issue where you added at least one theme/\* label in this run, you MUST also call dispatch_workflow" — was followed correctly. Zero spurious dispatches; zero missing dispatches under that contract.

The nuance

This sweep happened to pick a population where every themed issue had been themed previously by a human (PAT-driven, which DID trigger project-sync at the time). So the live sweep didn't exercise the "agent applies brand-new theme inside a sweep" code path — Test 1 covered that for the fast path, and the same handler/dispatcher is used in both modes (one tools list, one safe-outputs config), so the code path is equivalent.

If we ever observe a future issue that the panel themes for the first time and that issue ends up missing from the board, the prompt rule to revisit is: "dispatch on every triage-touched issue carrying a theme/* label, regardless of who applied it". That would be one-line prompt tightening and a bump of dispatch_workflow.max from 10 to match add_labels.max. Not warranted now — current design correctly avoids redundant board writes for already-synced items.

Verdict

The fix delivered in this PR works in production. Test 1 is a clean end-to-end proof: agent-applied theme on issue #841 produced a single workflow_dispatch to project-sync.yml, the dispatched run picked up the issue's GraphQL node ID via the INPUT_CONTENT_ID env var (script-injection-safe), and all 5 PGS board fields landed correctly. The original "themed-by-panel issues silently skip the board" regression that motivated this PR is closed.

Recommend: keep the worktree for one more sweep cycle to catch a sweep that themes a brand-new issue, then close out. No code change needed in the meantime.

@danielmeppiel danielmeppiel mentioned this pull request Apr 27, 2026
3 tasks
danielmeppiel added a commit that referenced this pull request Apr 27, 2026
* chore(release): cut 0.9.4

CHANGELOG entry for 0.9.4 covers all 7 PRs merged since v0.9.3:

- #974 SKILL_BUNDLE day-0 install parity (Added)
- #954 automate apm-triage-panel workflow (Added)
- #970 python-architect mermaid classDiagram trap (Changed)
- #911 REQUESTS_CA_BUNDLE TLS validation (Fixed)
- #971 triage-panel project-sync dispatch (Fixed)
- #910 CLI consistency cleanup (Fixed)
- #958 issue templates label taxonomy (Fixed)
- #953 docs auto-deploy after bot-cut releases (Fixed)

Open milestone 0.9.4 issues (41) reassigned to 0.9.5.

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

* chore(changelog): tighten 0.9.4 entries (so-what for developers)

Refactor per Keep-a-Changelog spirit: lead with developer impact,
trim agent-internals prose, group maintainer-only changes.

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

* chore(changelog): add #660 install.sh air-gapped entry to 0.9.4

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants