Repository navigation
Warn when GitHub App-authored safe-outputs can self-cancel comment-triggered workflows via shared concurrency #21975
Description
Activity
- locked and limited conversation to collaborators
on Mar 20, 2026 - unlocked this conversation
on Mar 20, 2026 This is a good one. We need to prevent cycles.
Reacted by Samuel Kahessay- linked a pull request that will close this issueWarn when GitHub App safe-outputs can self-cancel comment-triggered workflows via shared concurrency #21979
on Mar 20, 2026 /scout investigate counter measures
Reacted by github-actionsgithub-actions commented
on Mar 20, 2026 on Mar 20, 2026 – with GitHub ActionsContributorMore actions🔭 Recon complete! Scout has charted the territory. Map ready! 🗺️
github-actions commented
on Mar 20, 2026 on Mar 20, 2026 – with GitHub ActionsContributorMore actions🔍 Scout Research Report
Triggered by
@pelikhanExecutive Summary
This self-cancellation pattern is a known problem across the GitHub Actions ecosystem (seen in
claude-code-action,dependabot, and many bot-comment workflows). GitHub has no native trigger-level filtering by comment author. However, several countermeasures are available — from simple concurrency key tweaks to gh-aw compiler-level guardrails. A particularly notable finding: gh-aw already emits `` markers in its own comments, which could be used as a detection signal.Click to expand detailed findings
Research Findings
Countermeasure 1: Actor-based concurrency key isolation (most robust)
Use
contains(github.actor, '[bot]')to detect App-authored runs and route them to a uniquegithub.run_id-based key, preventing them from ever matching the primary run's group:concurrency: group: >- gh-aw-${{ github.workflow }}-${{ contains(github.actor, '[bot]') && github.run_id || github.event.issue.number || github.event.inputs.issue_number || github.run_id }} cancel-in-progress: true
This is the cleanest approach — passive bot-triggered runs are fully isolated from real command runs. GitHub Apps always have
github.actorin the formappname[bot], makingcontains(github.actor, '[bot]')a reliable signal.
Countermeasure 2: Conditional
cancel-in-progressGitHub Actions supports expressions in
cancel-in-progress. Setting it based on the actor type prevents bot-triggered runs from cancelling the primary run, while still serializing real command runs:concurrency: group: >- gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.event.inputs.issue_number || github.run_id }} cancel-in-progress: ${{ !contains(github.actor, '[bot]') }}
Source: GitHub Docs — conditional cancel-in-progress and community/discussions/69704
Countermeasure 3: gh-aw HTML comment marker (novel finding)
gh-aw already marks its own comments with ``. This is visible in the live comment on this issue. This metadata could serve as a first-class signal in concurrency expressions or job-level guards:
concurrency: group: >- gh-aw-${{ github.workflow }}-${{ contains(github.event.comment.body, 'gh-aw-comment-type') && github.run_id || github.event.issue.number || github.event.inputs.issue_number || github.run_id }}
This approach is gh-aw-specific and more precise than a generic bot actor check. Since gh-aw controls what's written into comments, it could also add a dedicated marker like `` for exactly this purpose.
Countermeasure 4: Job-level guard + comment.id isolation
The pattern used as a workaround in anthropics/claude-code-action#794 (the exact same problem independently discovered there):
concurrency: group: ${{ github.workflow }}-${{ github.event.comment.id || github.event.issue.number || github.run_id }} cancel-in-progress: false jobs: agent: if: github.event.comment.user.type != 'Bot' # ...
Trade-off: Creates duplicate skipped runs in the Actions tab, but prevents self-cancellation. This is the current ecosystem workaround for workflows that can't touch the concurrency group key.
Countermeasure 5: Compiler-level warning (gh-aw product action)
The dangerous combination is detectable at compile time:
cancel-in-progress: trueat workflow levelissue_commentor similar comment-based triggersafe-outputs.github-appconfigured- Concurrency group references
issue.numberorinputs.issue_number
A warning could read: "App-authored safe-outputs on this workflow can re-trigger it via
issue_comment, and those passive runs may cancel this run. Consider usingcancel-in-progress: ${{ !contains(github.actor, '[bot]') }}or isolating bot-triggered runs to a unique concurrency key."
Countermeasure 6: First-class "passive event" frontmatter (gh-aw, longer-term)
gh-aw could offer a
safe-outputs.passive-event-guard: truefrontmatter option that auto-generates the correct concurrency expression to isolate passive follow-up events. This would turn a footgun into a one-line opt-in.
Platform limitation (GitHub constraint)
GitHub has no native support for filtering workflow triggers by comment author. The community has been requesting this for years (community/discussions/41518 — locked with 97+ comments). Any solution must work within workflow YAML expressions.
cancel-in-progressaccepts full expressions, and concurrency group keys accept full expressions — both are the right levers.Recommendations
- Immediate docs fix: Document that
safe-outputs.github-app+issue_commenttrigger +cancel-in-progress: truecreates a self-cancellation footgun. Show the${{ !contains(github.actor, '[bot]') }}pattern as the fix. - Quick compiler warning: Detect the dangerous combination and emit a warning with the recommended fix.
- Leverage existing markers: gh-aw already emits `` — either document its use in concurrency expressions, or add a dedicated
gh-aw-passivemarker for this purpose. - Preferred fix pattern: Countermeasure 2 (conditional
cancel-in-progress) is the simplest change with the least disruption to existing concurrency group shapes.
Key Sources
References: §23347703392
@copilot implement counter measure 1
Countermeasure 1: Actor-based concurrency key isolation (most robust)
Use contains(github.actor, '[bot]') to detect App-authored runs and route them to a unique github.run_id-based key, preventing them from ever matching the primary run's group:
concurrency:
group: >-
gh-aw-${{ github.workflow }}-${{
contains(github.actor, '[bot]') && github.run_id ||
github.event.issue.number ||
github.event.inputs.issue_number ||
github.run_id
}}
cancel-in-progress: true
This is the cleanest approach — passive bot-triggered runs are fully isolated from real command runs. GitHub Apps always have github.actor in the form appname[bot], making contains(github.actor, '[bot]') a reliable signal- linked a pull request that will close this issuefeat: prevent GitHub App safe-outputs from self-cancelling issue_comment workflows #22825
on Mar 25, 2026 - added a commit that references this issue
on Mar 25, 2026
Context
I hit a concurrency failure mode in a comment-triggered workflow that uses:
concurrency.cancel-in-progress: trueworkflow_dispatchwith anissue_numberinputissue_commentas a triggersafe-outputs.github-appfor comments and other write operationsThe issue is not that the concurrency key is empty or malformed. The key is
correctly populated. The problem is that a passive follow-up run triggered by
the workflow's own App-authored comment can resolve to the same workflow-level
concurrency group as the primary run that produced the comment.
When that happens, GitHub Actions cancels the original run mid-flight.
This looks like a gh-aw workflow-authoring footgun at the boundary between:
safe-outputs.github-appbehaviorProblem
gh-aw explicitly supports using a GitHub App token for all safe output
operations.
From
.github/aw/github-agentic-workflows.md:safe-outputs.github-app"uses it for all safe output operations"safe-outputs.concurrency-groupexists specifically to control safe-outputsjob concurrency and uses
cancel-in-progress: falseThat means safe-outputs writes are a first-class product surface, not an
incidental repo customization.
The failure mode is:
dispatch input
safe_outputsposts a comment using a GitHub App tokenissue_commentrun of the same workflowcancel-in-progress: truecancels the original runThis is especially easy to hit in workflows that serialize by issue number and
listen to
issue_commentfor real slash commands.In other words: App-authored safe outputs can re-enter the same workflow and
collide with the run that emitted them.
I am filing this primarily as a warning/docs/guardrail issue, not as a demand
for gh-aw to infer arbitrary user intent from custom concurrency expressions.
Why this seems upstream-relevant
I am not claiming gh-aw can guess every custom concurrency expression.
The upstream issue is that gh-aw currently exposes all the ingredients for this
failure mode without warning or guardrails:
safe-outputs.github-appcancel-in-progress: trueactivation
workflow-level self-trigger collision this creates
Right now it is easy to build a workflow that is individually valid in each
part, but self-cancels once those parts interact.
I do not think this necessarily implies a universal compiler fix for arbitrary
user-defined concurrency expressions. But it does look like something gh-aw
should warn about or document more explicitly.
Observed reproduction
Observed in:
samuelkahessay/daily-habit-trackerv0.62.223341636440Timeline:
workflow_dispatchfor issue#15#15agentcompletes successfullysafe_outputsposts a comment on issue#15using GitHub App authissue_commentruns are triggeredcancel-in-progress: truecancels the original runsafe_outputsand downstream post-agent jobs are marked cancelledsurvive
Important detail:
issue_commentrun does not need to reachpre_activationor prove it is actionable to cancel the original run
Concrete local shape that reproduces it
This was reproducible with a workflow-level concurrency pattern like:
paired with:
and an
issue_commenttrigger in the same workflow.That is enough for a passive App-authored follow-up comment to enter the same
concurrency group as the original targeted run.
Minimal standalone repro shape
This does not depend on any repo-specific prompt content. The minimal shape is:
If that workflow is dispatched with an
issue_number, andsafe_outputspostsan App-authored comment back onto that same issue, the passive
issue_commentfollow-up run can enter the same workflow-level concurrency group and cancel
the original run.
Product evidence
gh-aw explicitly documents GitHub App auth for safe-outputs
From
.github/aw/github-agentic-workflows.md:safe-outputs.github-appis documented as the token used "for all safeoutput operations"
So App-authored follow-up writes are a supported configuration, not an odd
hack.
gh-aw already recognizes that safe-outputs needs its own concurrency handling
From
docs/src/content/docs/reference/concurrency.md:safe-outputs.concurrency-groupexists to serialize thesafe_outputsjobcancel-in-progress: falseSo gh-aw already treats safe-outputs concurrency as something that needs
product-level controls. The missing piece is the interaction with
workflow-level concurrency when safe-outputs itself can trigger new runs.
safe-outputs.concurrency-groupdoes not protect against this workflow-level collisionThe existing
safe-outputs.concurrency-groupcontrol is useful, but it onlychanges concurrency on the
safe_outputsjob itself.It does not prevent a passive comment-triggered workflow run from entering the
same workflow-level concurrency group and cancelling the originating run.
That makes this particular failure mode easy to miss even for users who are
already trying to use the safe-outputs concurrency controls correctly.
Top-level GitHub App config falls through to safe-outputs automatically
From
pkg/workflow/safe_outputs_config.go, lines 675-682:So a workflow that configures
github-appat the top level automatically getsApp-authored safe-outputs without explicitly setting
safe-outputs.github-app.That makes this collision easier to hit — any top-level App config is enough.
Impact
This can cause the original run to self-cancel after the main agent work is
done but before all post-agent jobs complete.
In the observed case that meant:
safe_outputsended as cancelledAny workflow with the same trigger/concurrency pattern is exposed, especially:
issue_commentissue or PR they serialize on
Expected behavior
gh-aw should make this interaction harder to trip over.
At minimum, users should not have to discover by incident that:
safe-outputs.github-appcan re-trigger the same workflow via commentsreach activation
Proposed fixes
I think any of these would be a reasonable upstream response. A docs-only or
warning-only fix would already be useful:
Docs warning in concurrency and safe-outputs references
comment-based workflows and collide with workflow-level concurrency groups
safe-outputs.concurrency-groupdoes not protectagainst this specific workflow-level self-cancellation pattern
Heuristic compiler warning for the dangerous combination
cancel-in-progress: truesafe-outputs.github-appis enabledoriginating run
limited to common patterns or skipped in favor of docs only
Provide a documented passive-event pattern
github.run_id, while preserving per-issue serialization for real slashcommands / targeted dispatch
Longer-term: add a first-class helper or frontmatter abstraction
real command activations in concurrency configuration
Workaround
The local workaround was to special-case passive
issue_commentevents so theyget a unique workflow concurrency key:
That preserves serialization for real
/testcommands while preventingApp-authored follow-up comments from cancelling the primary run.
Environment
samuelkahessay/daily-habit-trackerv0.62.223341636440.github/aw/github-agentic-workflows.mddocs/src/content/docs/reference/concurrency.mdpkg/workflow/safe_outputs_config.go(lines 675-682)