Skip to content

fix(#5188): recognize org bot actors in dispatch routing#5415

Closed
ggallen wants to merge 1 commit into
fullsend-ai:mainfrom
ggallen:fix/5188-org-bot-dispatch-auth
Closed

fix(#5188): recognize org bot actors in dispatch routing#5415
ggallen wants to merge 1 commit into
fullsend-ai:mainfrom
ggallen:fix/5188-org-bot-dispatch-auth

Conversation

@ggallen

@ggallen ggallen commented Jul 21, 2026

Copy link
Copy Markdown
Member

Summary

The collaborator permission API does not recognize GitHub App bot accounts (role_name comes back empty), so any authorization check that delegates to it always fails for a bot actor — even though the bot has legitimate access via its app installation grant. This broke two independent dispatch paths silently:

This is the same bug recurring at two call sites, not two different bugs, so it gets one fix.

Fixes #5188

Changes

  • Add is_org_bot() recognizing any org-prefixed bot identity (${ORG_NAME}-*[bot] — coder, review, triage, retro, prioritize, present and future). Third-party bots (renovate[bot], dependabot[bot], etc.) still fall through to the normal collaborator-permission check.
  • Apply is_org_bot as a bypass on both pull_request_target.opened/synchronize/ready_for_review (review dispatch) and issues.opened/edited (triage dispatch), in both dispatch.yml (per-org) and reusable-dispatch.yml (per-repo).
  • Extract the routing helpers into internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh with unit tests (dispatch-route-helpers-test.sh) so this logic is testable outside the workflow YAML, and compact the remaining inline helpers to keep both workflow files under the size limit (ADR-0005).
  • Register the new test in the Makefile script-test target.
  • Annotate ADR 0054 (append-only note) documenting the fix and both prior symptoms (Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188, dispatch: retro-filed proposal issues skip auto-triage under ADR 0054 authorization gate #2636).

Testing

  • dispatch-route-helpers-test.sh — 22/22 passing (is_org_bot across org/third-party bots and all agent roles, has_label incl. a set -u nounset regression test, is_issue_author)
  • go test ./internal/scaffold/... — passing, including updated TestDispatchWorkflowContent
  • pre-commit run — all hooks pass, including ShellCheck and lint-workflow-size (dispatch.yml at 492/500 lines)
  • scan-secrets clean

Review history

This PR went through two review rounds (fullsend-ai-review + Qodo) that flagged and got fixed: a CI-breaking stale test assertion, a set -u nounset bug in has_label, dead code, a misleading file header, and several doc/consistency gaps. All inline threads were replied to and resolved. The remaining open finding is a protected-path note (.github/ changes always require human approval regardless of content) — not an actionable defect.

Context

Originally scoped narrowly to #5188 (code agent's own PRs never reviewed). While preparing this PR, found the identical root cause independently breaks issue-triage dispatch too (#2636, previously papered over with a label-based workaround rather than a real fix) — broadened the fix to cover both rather than filing a near-duplicate follow-up.

@ggallen
ggallen requested a review from a team as a code owner July 21, 2026 13:47
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix dispatch routing for org GitHub App bots in pull_request_target

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Allow org coder/code bot actors to pass pull_request_target review dispatch gating
• Extract dispatch routing helpers into a shared, testable shell module
• Add unit tests and wire them into the Makefile script-test target
Diagram

graph TD
  A{{"pull_request_target event"}} --> B["dispatch.yml routing"] --> E{"is_org_bot?"} --> F["STAGE=review"]
  A --> C["reusable-dispatch.yml routing"] --> E
  B --> D["dispatch-route-helpers.sh"] --> E
  C --> D
  E -->|"no"| G["GitHub collaborator permission API"]
  H["Makefile script-test"] --> I["dispatch-route-helpers-test.sh"] --> D
  G -->|"triage+"| F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use author_association / sender type instead of username allowlist
  • ➕ Avoids hard-coding specific bot usernames
  • ➕ Uses event payload fields already present in workflows (no extra API call)
  • ➖ GitHub App bot identities can appear as different associations depending on context
  • ➖ May accidentally broaden who is allowed to trigger review dispatch without an explicit allowlist
2. Check GitHub App installation or actor type via GitHub API
  • ➕ More principled identification of GitHub App bots than matching strings
  • ➕ Could scale to multiple bot identities without adding more patterns
  • ➖ More API calls/permissions and more failure modes in workflows
  • ➖ Harder to unit-test reliably and may still not map cleanly to routing intent

Recommendation: The PR’s approach (explicit allowlist for ${ORG_NAME}-coder[bot] and ${ORG_NAME}-code[bot] with a short-circuit before collaborator permission checks) is the safest targeted fix: it unblocks known org bots without expanding access for arbitrary bots, and preserves existing permission semantics for humans. Extracting the logic into a shared script with tests is also the right tradeoff given workflow size limits and the prior silent-failure behavior.

Files changed (5) +232 / -29

Bug fix (2) +14 / -29
reusable-dispatch.ymlBypass permission API for org bots in PR routing +7/-14

Bypass permission API for org bots in PR routing

• Adds an is_org_bot helper and updates pull_request_target opened/synchronize/ready_for_review routing to allow org bot actors before falling back to collaborator-permission checks. Also compacts inline helper definitions to keep the workflow within size constraints.

.github/workflows/reusable-dispatch.yml

dispatch.ymlAllow org bots through pull_request_target review dispatch gate +7/-15

Allow org bots through pull_request_target review dispatch gate

• Introduces is_org_bot and updates pull_request_target routing to accept org coder/code bot actors without requiring collaborator role_name resolution. Keeps remaining authorization helpers compact to stay under workflow size limits.

internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml

Refactor (1) +83 / -0
dispatch-route-helpers.shExtract shared dispatch routing helpers and add org bot detection +83/-0

Extract shared dispatch routing helpers and add org bot detection

• Creates a shared shell module for dispatch workflows, including collaborator permission checks via gh api and a new is_org_bot allowlist for org coder/code bot identities. Provides reusable helpers (is_authorized, is_event_actor_authorized, is_issue_author, has_label) to keep workflow YAML smaller and logic testable.

internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh

Tests (1) +134 / -0
dispatch-route-helpers-test.shAdd unit tests for dispatch-route helper functions +134/-0

Add unit tests for dispatch-route helper functions

• Introduces a small bash test harness covering is_org_bot, has_label, and is_issue_author across 17 cases, validating both positive and negative scenarios. Runs functions in a clean env to simulate workflow execution context.

internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers-test.sh

Other (1) +1 / -0
MakefileRun dispatch routing helper tests in script-test +1/-0

Run dispatch routing helper tests in script-test

• Adds dispatch-route-helpers-test.sh to the script-test target so routing logic is validated alongside other scaffold script tests.

Makefile

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:48 PM UTC · Completed 2:07 PM UTC
Commit: 46045f3 · View workflow run →

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

Site preview

Preview: https://5d94649f-site.fullsend-ai.workers.dev

Commit: 1ba11e93b235dbbb033419efc86b8ed44d33a934

@qodo-code-review

qodo-code-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 61 rules

Grey Divider


Remediation recommended

1. Nounset breaks has_label ✓ Resolved 🐞 Bug ≡ Correctness
Description
dispatch-route-helpers.sh documents ISSUE_LABELS as optional, but has_label expands
${ISSUE_LABELS} without a default, which will hard-fail under set -u when ISSUE_LABELS is
unset. If/when this helper is sourced by workflows (which run set -euo pipefail), routing can
terminate early instead of returning “label not present.”
Code

internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh[R73-83]

+# Helper: check if a label is present in a comma-separated list.
+# Usage: has_label <name> [label_csv]  (defaults to ISSUE_LABELS)
+has_label() {
+  local needle="$1"
+  local csv="${2:-${ISSUE_LABELS}}"
+  IFS=',' read -ra labels <<< "${csv}"
+  for l in "${labels[@]}"; do
+    [[ "$l" == "$needle" ]] && return 0
+  done
+  return 1
+}
Relevance

⭐⭐⭐ High

Nounset-guarding optional env vars is commonly accepted (e.g., ${VAR:-...} in #2456/#2459).

PR-#2456
PR-#2459

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The script explicitly treats ISSUE_LABELS as optional, but the implementation expands it without
:- guarding; with set -u, that expansion is a runtime error rather than a clean “not found”
return.

internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh[12-17]
internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh[73-82]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`has_label()` uses `local csv="${2:-${ISSUE_LABELS}}"`, which expands `ISSUE_LABELS` even though the script documents it as optional. In shells with `set -u`, this raises an “unbound variable” error and aborts execution.

### Issue Context
This helper is intended to be sourced by dispatch workflows (which run with `set -euo pipefail`). Even though current workflows inline this logic, if they later switch to sourcing this helper, routing may fail unexpectedly.

### Fix Focus Areas
- internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh[73-83]
- internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers-test.sh[97-116]

### Suggested fix
- Change the default expansion to be nounset-safe, e.g.:
 - `local csv="${2:-${ISSUE_LABELS:-}}"`
- Consider applying the same `${VAR:-}` pattern anywhere else the file claims an env var is optional.
- Add a unit test that runs `has_label` without setting `ISSUE_LABELS` (and with `set -u` enabled in the subshell) to prevent regressions.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unused dispatch-route-helpers.sh added 📘 Rule violation ⌂ Architecture
Description
internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh was introduced as a shared
routing abstraction (and even claims it is sourced by both dispatch workflows), but both dispatch
workflows still inline equivalent helper logic and never source the script. This leaves duplicated,
drifting logic and means the new unit tests exercise unused code rather than the production routing
paths, violating the requirement to avoid single-use abstractions.
Code

internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh[R1-66]

+#!/usr/bin/env bash
+# dispatch-route-helpers.sh — Shared routing helpers for dispatch workflows.
+#
+# Sourced by both dispatch.yml (per-org) and reusable-dispatch.yml (per-repo)
+# to keep routing logic testable and within workflow size limits (ADR-0005).
+#
+# Required env vars (set by the caller before sourcing):
+#   GITHUB_REPOSITORY  — full repo name (org/repo)
+#   ORG_NAME           — repository owner / org name
+#   GH_TOKEN           — GitHub token for API calls
+#
+# Optional env vars (used by routing callers):
+#   COMMENT_USER_LOGIN, COMMENT_BODY, COMMENT_USER_TYPE,
+#   COMMENT_AUTHOR_ASSOC, ISSUE_LABELS, PR_LABELS,
+#   ISSUE_USER_LOGIN, PR_USER_LOGIN, EVENT_SENDER_LOGIN,
+#   REVIEW_USER_LOGIN, REVIEW_STATE, TRIGGERING_LABEL,
+#   PR_HEAD_REPO, PR_BASE_REPO
+
+# Collaborator role_name vs min (write|triage). See #5223 / ADR 0054.
+# API resolves org membership regardless of visibility (gh-aw-mcpg#2862).
+has_repo_permission() {
+  local username="${1:-}" min="${2:-write}" role api_err
+  [[ -z "${username}" ]] && return 1
+  api_err=$(mktemp) || {
+    echo "::warning::Failed to create temp file for permission check of ${username}" >&2
+    return 1
+  }
+  role=$(gh api "repos/${GITHUB_REPOSITORY}/collaborators/${username}/permission" \
+    --jq '.role_name' 2>"${api_err}") || {
+    echo "::warning::Permission API call failed for ${username}: $(cat "${api_err}")" >&2
+    rm -f "${api_err}"
+    return 1
+  }
+  rm -f "${api_err}"
+  case "${role}" in
+    admin|maintain|write) return 0 ;;
+    triage) [[ "${min}" == "triage" ]] && return 0 || return 1 ;;
+    *) return 1 ;;
+  esac
+}
+
+has_write_permission() { has_repo_permission "${1:-}" write; }
+
+# Slash-command auth; optional $1 = write|triage (default write).
+is_authorized() {
+  has_repo_permission "${COMMENT_USER_LOGIN}" "${1:-write}"
+}
+
+# Event-actor auth; $1=user, optional $2 = write|triage (default write).
+is_event_actor_authorized() {
+  has_repo_permission "${1:-}" "${2:-write}"
+}
+
+# Check whether a username is a known org bot account.
+# Matches the org's own coder and code bots: ${ORG_NAME}-coder[bot],
+# ${ORG_NAME}-code[bot]. These are GitHub App bot identities that the
+# collaborator permission API does not recognize (#5188).
+is_org_bot() {
+  local username="${1:-}"
+  [[ -z "${username}" ]] && return 1
+  [[ -z "${ORG_NAME:-}" ]] && return 1
+  case "${username}" in
+    "${ORG_NAME}-coder[bot]"|"${ORG_NAME}-code[bot]") return 0 ;;
+    *) return 1 ;;
+  esac
+}
Relevance

⭐⭐ Medium

Mixed history: script extraction sometimes accepted (#448) but other dedup/extraction requests
rejected (#390).

PR-#448
PR-#390

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062065 requires that new abstractions be reused across multiple production call
paths, yet the new dispatch-route-helpers.sh file is added while both
.github/workflows/reusable-dispatch.yml and
internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml continue to define routing helper
functions inline (e.g., has_repo_permission, is_authorized, is_event_actor_authorized,
is_issue_author, is_org_bot, has_label) with no source of the helper script. Additionally,
the helper’s header comment asserts it is sourced by the workflows, but the workflows shown do not
do so, implying the accompanying tests validate the new script rather than the active routing logic
used in production workflows.

Rule 1062065: Avoid single-use abstractions; require reuse across multiple call paths
internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh[1-66]
.github/workflows/reusable-dispatch.yml[125-168]
internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[50-98]
internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh[2-6]
internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[50-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A shared helper abstraction (`internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh`) and its tests were added to centralize routing logic, but the dispatch workflows still implement the same helper functions inline and never `source` the script. This makes the abstraction effectively unused/single-use, increases drift/maintenance risk, and can create a false sense of coverage because the new tests validate code paths not exercised by the production workflows.

## Issue Context
- Compliance requirement (PR Compliance ID 1062065) expects new abstractions to be reused across multiple production call paths rather than introduced as unused/single-use layers.
- The helper script’s header comment states it is sourced by both dispatch workflows.
- The actual workflows still contain inline definitions of routing helpers (e.g., `has_repo_permission`, `is_authorized`, `is_event_actor_authorized`, `is_issue_author`, `is_org_bot`, `has_label`) in their route steps, with no `source` of the new helper script.
- As a result, the unit tests for `dispatch-route-helpers.sh` may not be validating the authoritative routing logic that the workflows actually execute.

## Fix Focus Areas
- .github/workflows/reusable-dispatch.yml[125-168]
- internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml[50-99]
- internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh[1-83]
- internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers-test.sh[1-134]
- Makefile[146-151]

## Implementation guidance
Choose one consistent direction:
1) **Preferred**: Update both workflows’ routing steps to `source internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh` and remove/trim the duplicated inline helper function blocks (keeping behavior identical). Ensure any required environment variables are exported before sourcing and make the helper safe for the workflow shell settings (e.g., `set -u` safety) so sourcing doesn’t introduce runtime failures.

2) If workflow constraints intentionally prevent sourcing: stop claiming in the helper header that it is sourced by the workflows, and remove `dispatch-route-helpers.sh`, its unit test script, and the Makefile registration to avoid carrying unused abstraction and tests that don’t reflect production behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh
Comment thread internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review

Findings

Medium

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:140 — Pre-existing (improved): ${username} is interpolated unsanitized into ::warning:: workflow commands in has_repo_permission. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output and tr '\n' ' ' to strip newlines, closing the previously unsanitized $(cat "${api_err}") path.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:104 — Pre-existing: when both COMMENT_USER_LOGIN and ISSUE_USER_LOGIN are unset or empty, is_issue_author returns true because empty-string equality evaluates to true in bash [[ ]]. Not practically reachable — both variables are always set from GitHub event context for issue_comment events, the only event type that calls this function.

  • [naming-consistency] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md — ADR 0054 body still references has_write_permission in multiple places. The PR adds (renamed has_repo_permission — see note below) annotations to three references but misses one: the bullet point is_authorized() — delegates to has_write_permission for the comment author has no rename annotation, unlike the surrounding references that do.

Previous run

Review

Findings

Medium

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:142 — Pre-existing (improved): ${username} is interpolated unsanitized into ::warning:: workflow commands in has_repo_permission. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output and tr '\n' ' ' to strip newlines, closing the previously unsanitized $(cat "${api_err}") path.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:101 — Pre-existing: when both COMMENT_USER_LOGIN and ISSUE_USER_LOGIN are unset or empty, is_issue_author returns true because empty-string equality evaluates to true in bash [[ ]]. Not practically reachable — both variables are always set from GitHub event context for issue_comment events, the only event type that calls this function.

Previous run (2)

Review

Findings

Medium

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:134 — Pre-existing (improved): ${username} is interpolated unsanitized into ::warning:: workflow commands in has_repo_permission. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output and tr '\n' ' ' to strip newlines, closing the previously unsanitized $(cat "${api_err}") path.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:57 — Pre-existing: when both COMMENT_USER_LOGIN and ISSUE_USER_LOGIN are unset or empty, is_issue_author returns true because empty-string equality evaluates to true in bash [[ ]]. Not practically reachable — both variables are always set from GitHub event context for issue_comment events, the only event type that calls this function.

  • [documentation-accuracy] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:8 — Code comment references (ADR-0005) to justify extracting routing logic for testability, but ADR-0005 is about the forge abstraction layer, not workflow testability or script extraction. Consider removing the ADR-0005 citation or replacing with the correct ADR reference.

  • [code-organization] internal/scaffold/fullsend-repo/.github/workflows/dispatch.ymlis_org_bot() uses compressed single-line style in dispatch.yml vs expanded multi-line style in reusable-dispatch.yml and dispatch-route-helpers.sh. This is a documented tradeoff driven by the lint-workflow-size constraint (dispatch.yml at 492/500 lines, ADR-0005); expanding would push the file over the limit.

Previous run (3)

Review

Findings

Medium

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:134 — Pre-existing (improved): ${username} is interpolated unsanitized into ::warning:: workflow commands in has_repo_permission. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output and tr '\n' ' ' to strip newlines, closing the previously unsanitized $(cat "${api_err}") path.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:115 — Pre-existing: when both COMMENT_USER_LOGIN and ISSUE_USER_LOGIN are unset or empty, is_issue_author returns true because empty-string equality evaluates to true in bash [[ ]]. Not practically reachable — both variables are always set from GitHub event context for issue_comment events, the only event type that calls this function.

  • [scope-coherence] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers-test.sh — The PR adds 205 lines of unit tests (22 test cases) for the extracted routing helpers, representing ~37% of total additions. This is broader than the narrow bug-fix title suggests but is transparently disclosed in the PR description and follows ADR-0005's routing-logic-in-scripts testability principle.

  • [code-organization] internal/scaffold/fullsend-repo/.github/workflows/dispatch.ymlis_org_bot() uses compressed single-line style in dispatch.yml vs expanded multi-line style in reusable-dispatch.yml and dispatch-route-helpers.sh. This is a documented tradeoff driven by the lint-workflow-size constraint (dispatch.yml at 492/500 lines, ADR-0005); expanding would push the file over the limit.

Previous run (4)

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml, .github/workflows/reusable-fix.yml — These files are under the .github/ protected path. The PR links to issue Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188 and explains the rationale for the change (fix bot actor recognition in dispatch routing). Human approval is always required for protected-path changes, regardless of context.

  • [prompt-injection] .github/workflows/reusable-dispatch.yml:1117, .github/workflows/reusable-fix.yml:352 — Pre-existing: the "Pre-fetch review body" steps match review comments by .user.login string, which on the self-managed org path (${ORG_NAME}-review[bot]) could be spoofed by a third party squatting the GitHub App slug. Unlike the dispatch authorization bypass (which only advances a stage), this path feeds potentially attacker-authored text into the fix agent's prompt. The PR's ADR 0054 annotation (lines 288–304) explicitly acknowledges this residual risk and identifies performed_via_github_app.id pinning as the mitigation, marking it out of scope for Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188/dispatch: retro-filed proposal issues skip auto-triage under ADR 0054 authorization gate #2636. The shared vendor-App path (fullsend-ai-review[bot]) is not affected. Note: pre-fetch-prior-review.sh already performs performed_via_github_app.client_id provenance validation downstream, providing a defense-in-depth layer for the review agent's prior-review context that the fix agent's review-body fetch lacks.

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:141 — Pre-existing: ${username} is interpolated unsanitized into ::warning:: workflow commands in has_repo_permission. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output and tr '\n' ' ' to strip newlines, closing the previously unsanitized $(cat "${api_err}") path.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:102 — Pre-existing: when both COMMENT_USER_LOGIN and ISSUE_USER_LOGIN are unset or empty, is_issue_author returns true because empty-string equality evaluates to true in bash [[ ]]. Not practically reachable — both variables are always set from GitHub event context for issue_comment events, the only event type that calls this function.

  • [scope-coherence] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers-test.sh — The PR adds 205 lines of unit tests (22 test cases) for the extracted routing helpers, representing ~37% of total additions. This is broader than the narrow bug-fix title suggests but is transparently disclosed in the PR description and follows ADR-0005's routing-logic-in-scripts testability principle.

Previous run (5)

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml, .github/workflows/reusable-fix.yml — These files are under the .github/ protected path. The PR links to issue Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188 and explains the rationale for the change (fix bot actor recognition in dispatch routing). Human approval is always required for protected-path changes, regardless of context.

  • [prompt-injection] .github/workflows/reusable-dispatch.yml:1107, .github/workflows/reusable-fix.yml:352 — Pre-existing: the "Pre-fetch review body" steps match review comments by .user.login string, which on the self-managed org path (${ORG_NAME}-review[bot]) could be spoofed by a third party squatting the GitHub App slug. Unlike the dispatch authorization bypass (which only advances a stage), this path feeds potentially attacker-authored text into the fix agent's prompt. The PR's ADR 0054 annotation (lines 288–304) explicitly acknowledges this residual risk and identifies performed_via_github_app.id pinning as the mitigation, marking it out of scope for Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188/dispatch: retro-filed proposal issues skip auto-triage under ADR 0054 authorization gate #2636. The shared vendor-App path (fullsend-ai-review[bot]) is not affected — that slug is controlled by the platform vendor. Note: pre-fetch-prior-review.sh already performs performed_via_github_app.client_id provenance validation downstream, providing a defense-in-depth layer for the review agent's prior-review context that the fix agent's review-body fetch lacks.

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:134 — Pre-existing: ${username} is interpolated unsanitized into ::warning:: workflow commands in has_repo_permission. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output and tr '\n' ' ' to strip newlines, closing the previously unsanitized $(cat "${api_err}") path.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:102 — Pre-existing: when both COMMENT_USER_LOGIN and ISSUE_USER_LOGIN are unset or empty, is_issue_author returns true because empty-string equality evaluates to true in bash [[ ]]. Not practically reachable — both variables are always set from GitHub event context for issue_comment events, the only event type that calls this function.

  • [scope-coherence] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers-test.sh — The PR adds 205 lines of unit tests (22 test cases) for the extracted routing helpers, representing ~37% of total additions. This is broader than the narrow bug-fix title suggests but is transparently disclosed in the PR description and follows ADR-0005's routing-logic-in-scripts testability principle.

Previous run (6)

Review

Findings

Medium

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:142 — Pre-existing: ${username} is interpolated unsanitized into ::warning:: workflow commands in has_repo_permission. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output and tr '\n' ' ' to strip newlines, closing the previously unsanitized $(cat "${api_err}") path.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:102 — Pre-existing: when both COMMENT_USER_LOGIN and ISSUE_USER_LOGIN are unset or empty, is_issue_author returns true because empty-string equality evaluates to true in bash [[ ]]. Not practically reachable — both variables are always set from GitHub event context for issue_comment events, the only event type that calls this function.

  • [scope-coherence] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:195 — The ADR annotation adds approximately 76 lines documenting the is_org_bot platform-constraint exception. While the content is technically accurate and properly references Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188/dispatch: retro-filed proposal issues skip auto-triage under ADR 0054 authorization gate #2636, the length borders on substantial content addition rather than a minor annotation. The note is architecturally appropriate as an extension of ADR 0054's authorization framework (it explicitly uses the ADR's own extension path), and the detail is driven by thoroughness in documenting security-critical trust paths.

Previous run (7)

Review

Findings

Medium

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:142 — Pre-existing: ${username} is interpolated unsanitized into ::warning:: workflow commands. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output.

  • [scope-coherence] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:212 — The ADR annotation adds 84 lines documenting the is_org_bot platform-constraint exception. While the content is technically accurate and properly references Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188/dispatch: retro-filed proposal issues skip auto-triage under ADR 0054 authorization gate #2636, the length borders on substantial content addition rather than a minor annotation. The note is architecturally appropriate as an extension of ADR 0054's authorization framework (it explicitly uses the ADR's own extension path), and the detail is driven by thoroughness in documenting security-critical trust paths.

  • [naming-alignment] .github/workflows/reusable-dispatch.yml:173is_org_bot() matches two distinct trust paths: shared vendor Apps fullsend-ai-<role>[bot] (org-agnostic) and self-managed ${ORG_NAME}-<role>[bot]. The name suggests only org-specific matching. The function's inline comment (7 lines in reusable-dispatch.yml, 13 lines in dispatch-route-helpers.sh) already documents both paths, mitigating confusion.

  • [code-formatting] internal/scaffold/fullsend-repo/.github/workflows/dispatch.ymlis_org_bot() uses compressed single-line style (loops, comments, blank-line separation) inconsistent with the expanded format in reusable-dispatch.yml and dispatch-route-helpers.sh. This is a known tradeoff driven by the lint-workflow-size constraint (dispatch.yml at 492/500 lines, ADR-0005); expanding would push the file over the limit.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:116 — Pre-existing: when both COMMENT_USER_LOGIN and ISSUE_USER_LOGIN are unset or empty, is_issue_author returns true because empty-string equality evaluates to true in bash [[ ]]. Not practically reachable — both variables are always set from GitHub event context for issue_comment events, the only event type that calls this function.

Previous run (8)

Review

Findings

Medium

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:137 — Pre-existing: ${username} is interpolated unsanitized into ::warning:: workflow commands. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output.
Previous run (9)

Review

Findings

Medium

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:136 — Pre-existing: ${username} is interpolated unsanitized into ::warning:: workflow commands. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output.

  • [authorization-scope] .github/workflows/reusable-dispatch.yml:282 — Pre-existing: the ready-to-code label path uses a broad [bot]$ regex while the updated paths use the stricter is_org_bot exact-match helper. Documented as intentional — labeling already requires write access, so the bot-to-bot handoff was pre-authorized.

  • [naming-consistency] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml — Pre-existing: dispatch.yml uses ISSUE_HAS_PR while reusable-dispatch.yml uses ISSUE_IS_PR for the same concept (github.event.issue.pull_request && 'true' || 'false'). AGENTS.md requires these files to share identical routing logic.

  • [code-organization] internal/scaffold/fullsend-repo/.github/workflows/dispatch.ymlis_org_bot() uses compressed single-line style in dispatch.yml vs expanded multi-line style in reusable-dispatch.yml and dispatch-route-helpers.sh. Comment also loses the detailed two-path explanation. Both justified by the lint-workflow-size constraint (dispatch.yml at 492/500 lines); full rationale is in dispatch-route-helpers.sh and ADR 0054.

Previous run (10)

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under the .github/ protected path. The PR links to issue Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188 and explains the rationale for the change (fix bot actor recognition in dispatch routing). Human approval is always required for protected-path changes, regardless of context.

  • [stale-reference] .github/workflows/reusable-fix.yml:352 — The PR fixes the REVIEW_BOT="${ORG_NAME}-review[bot]" single-identity pattern in reusable-dispatch.yml (pre-fetch review body step) to also match fullsend-ai-review[bot], but the identical pattern in reusable-fix.yml at line 352 is not updated. When a review was left by the shared vendor-owned fullsend-ai-review[bot] App (the default deployment model), the fix agent's jq filter won't match it, causing an empty review body and a hard failure at line 363–364.

  • [stale-reference] internal/scaffold/fullsend-repo/scripts/pre-fetch-prior-review.sh:14 — Same pattern: REVIEW_BOT="${ORG_NAME}-review[bot]" only matches the self-managed org identity, not the shared fullsend-ai-review[bot] vendor App. When a prior review was posted by fullsend-ai-review[bot], the jq filter at line 21 won't find it, causing the review agent to treat every dispatch as a first review (no prior SHA anchoring, no delta review).

Low

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:136 — Pre-existing: ${username} is interpolated unsanitized into ::warning:: workflow commands. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed 's/::/: /g' sanitization to the API error output.

  • [authorization-scope] .github/workflows/reusable-dispatch.yml:283 — Pre-existing: the ready-to-code label path uses a broad [bot]$ regex while the updated paths use the stricter is_org_bot exact-match helper. Documented as intentional — labeling already requires write access, so the bot-to-bot handoff was pre-authorized.

  • [naming-consistency] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml — Pre-existing: dispatch.yml uses ISSUE_HAS_PR while reusable-dispatch.yml uses ISSUE_IS_PR for the same concept (github.event.issue.pull_request && 'true' || 'false'). AGENTS.md requires these files to share identical routing logic.

  • [code-organization] internal/scaffold/fullsend-repo/.github/workflows/dispatch.ymlis_org_bot() uses compressed one-liner style in dispatch.yml vs expanded multi-line style in reusable-dispatch.yml. The compressed form may be driven by the lint-workflow-size constraint (dispatch.yml at 492/500 lines), but the formatting inconsistency reduces sync-ability between the two files.

Previous run (11)

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under the .github/ protected path. The PR links to issue Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188 and explains the rationale for the change (fix bot actor recognition in dispatch routing). Human approval is always required for protected-path changes, regardless of context.

  • [stale-reference] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:73 — The ADR body references has_write_permission at lines 73, 104–105, and 179, but this PR removes the function from both workflow files. The new annotation at the bottom acknowledges the rename to has_repo_permission, but the existing body text still describes has_write_permission as the active mechanism. ADR immutability rules constrain what can be changed in the body — a short inline cross-reference near each mention pointing to the annotation may be appropriate.

  • [intent-authorization] .github/workflows/reusable-dispatch.yml — The is_org_bot() bypass introduces a platform-constraint exception to ADR 0054's authorization requirement. The PR appends a detailed annotation documenting the exception, the GitHub API limitation, and the exact-match trust paths. Consider whether a new superseding ADR (rather than an annotation) is warranted for an exception of this scope — the annotation is lengthy and introduces a new authorization pathway.

  • [naming-consistency] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml:39 — Pre-existing: dispatch.yml uses ISSUE_HAS_PR while reusable-dispatch.yml uses ISSUE_IS_PR for the same concept (github.event.issue.pull_request && 'true' || 'false'). AGENTS.md requires these files to share identical routing logic.

Low

  • [authorization-scope] .github/workflows/reusable-dispatch.yml:278 — Pre-existing: the ready-to-code label path (mutation stage) uses a broad [bot]$ regex that matches any GitHub App bot, while the three paths updated by this PR use the stricter is_org_bot exact-match allowlist. The label-application step itself requires write access, providing an implicit authorization gate. See also: [inconsistent-bot-patterns] finding at this location.

  • [inconsistent-bot-patterns] .github/workflows/reusable-dispatch.yml:278 — Pre-existing: the ready-to-code and pull_request_review paths use broad [bot]$ regex while the three updated paths use exact-match is_org_bot. The distinction is deliberate (different authorization models for label-gated bot handoff vs. actor-initiated dispatch), but could benefit from an inline comment clarifying the rationale.

  • [workflow-command-injection] .github/workflows/reusable-dispatch.yml:134 — The ${username} variable is interpolated unsanitized into ::warning:: workflow commands. GitHub login names are restricted to [a-zA-Z0-9-], preventing injection in practice. The PR improves the actual injection vector by sanitizing the API error output with sed 's/::/: /g' (line 138).

  • [scope-alignment] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh — The routing helper extraction adds testability infrastructure beyond the narrow bug-fix scope of Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188. The PR description justifies this as necessary for the ADR-0005 workflow file size limit (dispatch.yml at 492/500 lines) and to enable testing the new is_org_bot function (22 tests).

Previous run (12)

Review

Findings

Medium

Low

  • [authorization-bypass] .github/workflows/reusable-dispatch.yml:163 — The is_org_bot() glob pattern matches any GitHub App bot whose slug starts with ${ORG_NAME}-. A third-party GitHub App installed by a repo admin with a matching slug would pass the check. Mitigated by: (1) app installation requires admin access exceeding triage-level privilege; (2) the bypassed paths only trigger observation stages (triage, review); (3) ADR 0054 annotation documents this trust boundary.

Labels: PR modifies dispatch routing logic and adds shell script testing infrastructure

Previous run (13)

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under the .github/ protected path. The PR links to issue Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188 and explains the rationale for the change (fix bot actor recognition in dispatch routing). Human approval is always required for protected-path changes regardless of context.

  • [divergence] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:28 — The reference helpers file declares sanitized_err with a separate local inside the error handler block (line 37), while both workflow inline copies declare it upfront in the function's initial local statement (local ... api_err sanitized_err). While both approaches are functionally equivalent in bash, the divergence means tests validate code with slightly different structure than what runs in production. The file's header states it should be "kept in sync by hand" with the inline copies.

Low

  • [authorization-bypass] .github/workflows/reusable-dispatch.yml:163 — The is_org_bot() glob pattern matches any GitHub App bot whose slug starts with ${ORG_NAME}-. A third-party GitHub App installed by a repo admin with a matching slug would pass the check. Mitigated by: (1) app installation requires admin access exceeding triage-level privilege; (2) bypassed paths only trigger observation stages (triage, review); (3) ADR 0054 annotation documents this trust boundary.

  • [architectural-coherence] .github/workflows/reusable-dispatch.yml:160 — The is_org_bot bypass creates a dual authorization path: (1) collaborator permission API, (2) org-prefixed bot identity check. The ADR 0054 annotation acknowledges this as a platform-constraint exception — authorization still derives from the org-name prefix, not arbitrary identity claims.

  • [naming-conventions] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:33 — Function comment for is_org_bot uses bare issue number references (#5188, #2636) while has_repo_permission uses See #5223 / ADR 0054 style. The difference reflects that is_org_bot doesn't relate to an ADR, so this is cosmetic.

Previous run (14)

Review

Findings

Medium

Low

  • [divergence] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:47 — The reference helpers file uses ${COMMENT_USER_LOGIN:-} (with default) in is_authorized() and is_issue_author(), and ${ISSUE_LABELS:-} in has_label(), while the inline copies in both dispatch.yml and reusable-dispatch.yml use bare forms without :- defaults. Both workflow run: blocks use set -euo pipefail, but the env block always sets these variables (even to empty string), so the bare expansions work in production. The test file validates the more defensive reference implementation, creating a minor fidelity gap between tested and deployed code.

  • [authorization-bypass] .github/workflows/reusable-dispatch.yml:163 — The is_org_bot() regex ^${ORG_NAME}-.+\[bot\]$ could theoretically match a third-party GitHub App whose name starts with ${ORG_NAME}-, if that app is installed on the target repo by a repo admin. However, the bypass is limited to read-only triage/review stages, and an admin who installs such an app already has greater privilege than is_org_bot grants. The ADR 0054 annotation documents this org-prefix trust boundary.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:55 — The is_org_bot regex expands ORG_NAME as a regex fragment, not a literal string. If an org name contained regex metacharacters, they would be interpreted as regex operators. GitHub org names are restricted to alphanumerics and hyphens, so this is not exploitable in practice.

Previous run (15)

Review

Findings

Medium

Low

  • [authorization-bypass] .github/workflows/reusable-dispatch.yml:163 — The is_org_bot() regex ^${ORG_NAME}-.+\[bot\]$ could theoretically match a third-party GitHub App whose name starts with ${ORG_NAME}-, if that app is installed on the target repo by a repo admin. However, the bypass is limited to read-only triage/review stages, and an admin who installs such an app already has greater privilege than is_org_bot grants. The ADR 0054 annotation documents this org-prefix trust boundary.

  • [architectural-coherence] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md — The ADR 0054 annotation documents the is_org_bot bypass and covers the GitHub API limitation, the org-prefix security boundary, and the third-party bot exclusion. Consider explicitly labeling this as a platform constraint exception to the "trust derives from repository permissions" principle for future readers.

  • [GHA-workflow-command-injection] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:10has_repo_permission emits ::warning:: with unsanitized $(cat "${api_err}"). While gh api stderr is not directly attacker-controlled, defense-in-depth sanitization (stripping :: sequences) would be prudent.

Previous run (16)

Review

Findings

Medium

Previous run (17)

Review

Findings

High

  • [test-integrity] internal/scaffold/scaffold_test.go:222TestDispatchWorkflowContent asserts that dispatch.yml contains has_write_permission, but this PR removes the has_write_permission() function from dispatch.yml. The test will fail after merge, breaking CI.
    Remediation: Remove or update the assertion at line 222, since has_write_permission() was unused (never called) in dispatch.yml.

Medium

  • [protected-path] .github/workflows/reusable-dispatch.yml — This file is under the .github/ protected path. The PR links to issue Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts #5188 and explains the rationale for the change. Human approval is always required for protected-path changes regardless of context.

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh — The file header says "Sourced by both dispatch.yml (per-org) and reusable-dispatch.yml (per-repo)" but the workflows define all functions inline and never source this file. The header is misleading.
    Remediation: Fix the header to say "Reference implementation for testing. Workflows define functions inline; this file exists for testability (ADR-0005)."

  • [stale-doc] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md:73 — ADR 0054 references has_write_permission() at lines 73, 104, 105, and 179. This PR removes has_write_permission from both workflow files. A note at lines 182–191 already acknowledges has_repo_permission replacing it, but the main body text is outdated.
    Remediation: Add a minor annotation noting the function name change, consistent with ADR immutability rules.

Low

  • [logic-error] internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml:79 — Inline is_org_bot() lacks the empty-string guards present in the helpers file version ([[ -z "${username}" ]] && return 1 and [[ -z "${ORG_NAME:-}" ]] && return 1). In practice, ORG_NAME (from github.repository_owner) is guaranteed non-empty, so this is a defense-in-depth gap rather than an exploitable issue. See also: same issue in reusable-dispatch.yml:153.

  • [logic-error] .github/workflows/reusable-dispatch.yml:153 — Same inline is_org_bot() empty-guard issue as dispatch.yml.

  • [consumer-completeness] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers.sh:43 — Helpers file retains has_write_permission() which is removed from both workflow files. Since the file exists only for testing, this is dead code creating a minor divergence.

  • [test-adequacy] internal/scaffold/fullsend-repo/scripts/dispatch-route-helpers-test.sh — Tests cover is_org_bot, has_label, and is_issue_author (17 cases), but don't test is_authorized, is_event_actor_authorized, or has_repo_permission. The API-dependent functions are difficult to unit test, so this gap is understandable.

  • [architectural-consistency] .github/workflows/reusable-dispatch.yml — Inline comments documenting is_authorized, is_event_actor_authorized, and is_issue_author function parameters were removed when compacting to one-liners.

  • [completeness] internal/scaffold/fullsend-repo/.github/workflows/dispatch.ymlis_org_bot() is only applied to pull_request_target opened|synchronize|ready_for_review, while the issues.labeled ready-to-code path uses a generic [bot]$ regex. The inconsistency appears intentional (different authorization requirements) but could be documented.

  • [missing-doc] docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md — ADR 0054 doesn't document the is_org_bot() pattern. A minor annotation (similar to the existing dispatch: relax authorization gate to Triage role for triage stage and auto-review #5223 note) would be appropriate.


Labels: PR modifies dispatch routing logic and fixes a bug in bot actor recognition

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added component/dispatch Workflow dispatch and triggers bug labels Jul 21, 2026
@ggallen
ggallen force-pushed the fix/5188-org-bot-dispatch-auth branch from 46045f3 to 989bf7a Compare July 21, 2026 14:45
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 2:46 PM UTC · Ended 2:59 PM UTC
Commit: 989bf7a · View workflow run →

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review July 21, 2026 14:59

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 21, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:46 PM UTC · Completed 2:59 PM UTC
Commit: 989bf7a · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:15 PM UTC · Completed 4:32 PM UTC
Commit: 1f1a1d5 · View workflow run →

@ggallen
ggallen force-pushed the fix/5188-org-bot-dispatch-auth branch from 1f1a1d5 to dabfa50 Compare July 21, 2026 16:41
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:43 PM UTC · Completed 4:58 PM UTC
Commit: dabfa50 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:04 PM UTC · Completed 5:20 PM UTC
Commit: efd0d27 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@ggallen
ggallen force-pushed the fix/5188-org-bot-dispatch-auth branch from efd0d27 to 2b6bf19 Compare July 21, 2026 17:45
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:46 PM UTC · Completed 6:00 PM UTC
Commit: 2b6bf19 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 22, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:59 PM UTC · Completed 2:16 PM UTC
Commit: bdbf027 · View workflow run →

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review squad pass 4 (Claude x2 + Grok, 3 agents). All three round-3 findings confirmed fixed via direct mutation testing against the live rendered files — the ||-anchored regexes, TestReusableFixWorkflowContent/TestReviewBotIdentitiesStayInSync, and the ADR prompt-injection addendum all hold up as intended. Two new findings this round, both surfaced by mutation-testing the new drift-guard tests themselves (HIGH: TestReviewBotIdentitiesStayInSync gives asymmetric protection across the three files it covers; MEDIUM: TestIsOrgBotConstantsStayInSync doesn't cover the want_role guard's comparison operator), plus one low-priority process note (no tracking issue for the deferred performed_via_github_app.id fix). Full detail inline.

Comment thread internal/scaffold/workflow_call_alignment_test.go
Comment thread internal/scaffold/workflow_call_alignment_test.go
Comment thread docs/ADRs/0054-require-authorization-on-all-agent-dispatch-paths.md Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 22, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:22 PM UTC · Completed 2:38 PM UTC
Commit: 6725415 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 22, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:23 PM UTC · Completed 4:40 PM UTC
Commit: 43020c6 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 22, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:55 PM UTC · Completed 5:10 PM UTC
Commit: a1635e3 · View workflow run →

The collaborator permission API does not recognize GitHub App bot
accounts (role_name comes back empty), so any authorization check that
delegates to it always fails for a bot actor — even though the bot has
legitimate access via its app installation grant. The identical root
cause independently broke five places across the dispatch/fix pipeline
that assumed a fullsend agent bot's identity is always
"${ORG_NAME}-<role>[bot]":

- pull_request_target.opened/synchronize/ready_for_review: PRs authored
  by fullsend's own agents never received automatic review, including
  after every fix-agent iteration (fullsend-ai#5188).
- issues.opened/edited: issues filed or edited by fullsend's own agents
  (e.g. the retro agent's proposal issues) never received automatic
  triage (fullsend-ai#2636).
- pull_request_review (changes_requested) -> fix auto-dispatch: checked
  REVIEW_USER_LOGIN == "${ORG_NAME}-review[bot]" directly.
- reusable-fix.yml's "Pre-fetch review body" step: same jq filter
  mistake, causing a hard failure ("nothing to fix") when the review
  came from the shared bot.
- pre-fetch-prior-review.sh: same mistake, silently treating every
  dispatch as a first review (no delta review, no prior SHA anchoring)
  when the prior review came from the shared bot.

All five assumed the bot's own installing-org name (ORG_NAME /
github.repository_owner) is baked into its identity. That's wrong for
fullsend's default deployment model: per ADR 0029/0059/0068, every
adopting org installs the SAME shared, vendor-owned fullsend-ai-<role>
Apps, so the bot identity is fixed regardless of which repo it operates
on. Each of these checks only "worked" by coincidence on
fullsend-ai/fullsend itself, where org name and vendor-App prefix
happen to collide, and was a no-op for every other adopting org. Found
via a human reviewer (waynesun09) on the first instance; a repo-wide
grep for the same pattern turned up the other four.

One fix, applied everywhere the bug appeared: is_org_bot() recognizes
two exact-match identities, never a prefix/suffix wildcard, optionally
restricted to one specific role via a second argument (needed at the
fix-dispatch gate, which must match specifically the review bot):

1. fullsend-ai-<role>[bot] for the five known agent roles (coder,
   review, triage, retro, prioritize), unconditionally, independent of
   ORG_NAME.
2. ${ORG_NAME}-<role>[bot] for the same five roles, for self-managed
   orgs running their own private per-role Apps (ADR 0029/0033).

Both paths match only a fixed, known set of role suffixes — never a
bare wildcard — so a third party can't forge the bypass by registering
a GitHub App named "${ORG_NAME}-<anything>[bot]" or
"fullsend-ai-<anything>[bot]". The same two-identity check is inlined
directly wherever jq can't call the bash helper (three separate jq
filters). ADR 0054 documents this as an explicit platform-constraint
exception.

Extract the routing helpers into scripts/dispatch-route-helpers.sh with
unit tests so is_org_bot and friends are testable outside the workflow
YAML, and compact the remaining inline helpers to keep both dispatch.yml
(per-org) and reusable-dispatch.yml (per-repo) under the workflow size
limit (ADR-0005). gh-api error text is sanitized before being embedded
in a GitHub Actions ::warning:: annotation, since an unsanitized "::"
sequence could otherwise be read as a workflow command.

Test coverage locks in all three bypass call sites (including the
review-role-restricted one) and, per a human reviewer's suggestion,
verifies the security-relevant is_org_bot constants (role list and both
identity match patterns) stay identical across the three hand-copied
implementations, guarding against the kind of silent drift that
produced earlier review rounds' divergence findings.

Signed-off-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
@ggallen
ggallen force-pushed the fix/5188-org-bot-dispatch-auth branch from a1635e3 to 1ba11e9 Compare July 22, 2026 18:29
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 22, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:31 PM UTC · Completed 6:47 PM UTC
Commit: 1ba11e9 · View workflow run →

@ifireball ifireball left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have discussed this issue multiple times - the solution for triggering on the bot actions is to have the bot prove they have sufficient access by setting labels! I do not thing we should merge this PR.

Also if we do end up agreeing to go down this path, at this point I would expect such a change to also be applied to the CEL-based dispatch path.

[[ -z "${username}" ]] && return 1
for role in ${FULLSEND_AGENT_ROLES}; do
[[ -n "${want_role}" && "${role}" != "${want_role}" ]] && continue
[[ "${username}" == "fullsend-ai-${role}[bot]" ]] && return 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this assumes the defaults set of GH apps - people can still use other apps.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair point — verified this is real: --app-set accepts any string (ValidateAppSet has no format requirement tying it to the org), and docs/guides/infrastructure/advanced-setup.md explicitly documents registering "multiple app sets on the same mint," which by definition can't all equal a single ${ORG_NAME}. Neither this check nor the collaborator-permission-API check it replaces has ever been able to recognize a non-conventional app-set bot (GitHub Apps aren't collaborators regardless of name), so this narrows an existing gap rather than introducing one. Properly closing it means threading the actual configured app-set prefix through as a repo variable at install time (nothing analogous to the existing FULLSEND_MINT_URL/FULLSEND_GCP_REGION variables exists for this yet) — that's installer + scaffold plumbing beyond this PR's routing-bug-fix scope, so I filed #5480 to track it and added a short note to the ADR (pending push, commit ae9930ed) acknowledging the gap where the ${ORG_NAME} assumption is stated.

@ifireball ifireball Jul 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The need to recognise bots by name evaporates if we require then to prove their access by having them proactively set labels instead of trying to sniff it via the GH API. This had been our approach for a long time, and I'm not sure why are we diverging from it now. (I wrote this on my main review comment as well, but those seem to always get lost in the noise)

if [[ -n "${ORG_NAME:-}" ]]; then
for role in ${FULLSEND_AGENT_ROLES}; do
[[ -n "${want_role}" && "${role}" != "${want_role}" ]] && continue
[[ "${username}" == "${ORG_NAME}-${role}[bot]" ]] && return 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this still makes string-based assumptions on the bot name, in practice bots can have strange names if people so desire.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same root concern as your comment on line 176 above — this pattern is string/name-based by necessity (GitHub Apps aren't identifiable any other way in these event payloads, and performed_via_github_app.id pinning is tracked separately as #5463 for the review-bot-specific gate). The specific gap you're pointing at here — that the name doesn't have to match ${ORG_NAME} at all — is now tracked as #5480, with an ADR note added (commit ae9930ed, pending push) acknowledging that a self-hosted operator can register an app set under any name.

Both `is_authorized` (slash commands) and `is_event_actor_authorized`
(event triggers) delegate to a shared `has_write_permission` helper that
calls the collaborator permission API. This ensures consistent behavior
across all paths.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are we (massively) updating an accepted ADR instead of sending-in a new one when we want to make drastic design changes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair question. The convention here is append-only dated notes ("Note (YYYY-MM-DD, #issue):" blockquotes) rather than editing the original decision text — this predates this PR: the #5223 note above (2026-07-17) already used this pattern to extend the authorization mechanism (adding the triage permission tier) without touching the accepted body text. This PR's notes follow the same convention to document a platform-constraint exception (bot identities aren't resolvable via the collaborator-permission API this ADR originally specified), not to reverse or rewrite the original decision — repository permissions are still the authorization source in both cases, just resolved two different ways.

That said, I think your broader point stands as a real design question: if the is_org_bot() pattern keeps needing amendment (as this thread and #5480 show), at what point does it stop being a footnote on a permission-check ADR and become its own decision worth a fresh ADR with its own context/consequences section? I don't have a strong view on where that line is, but if #5480's fix ends up being substantial (new install-time plumbing, a repo variable, etc.), that's a reasonable candidate for its own ADR rather than another note here.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review squad pass 5 (Claude + Grok, 2 agents). All three round-4 fixes verified effective via direct content inspection against the real files, not just re-reading the diff. One new finding this round: the wantRoleGuard assertion only anchors half of a compound && condition, leaving an -n→-z mutation undetected (full detail inline). Grok's independent assessment: this PR has reached a natural stopping point — rounds 1-4 fixed real gaps in the core fix and its test coverage, and this round's findings (including the one above) are narrow test-meta-coverage gaps rather than defects in the shipped fix itself. Recommend merging once the inline finding is addressed, without further review rounds on test-suite meta-coverage.

// the pull_request_review -> fix gate.
const wantRoleGuard = `"${role}" != "${want_role}"`
for name, content := range sources {
assert.Contains(t, content, wantRoleGuard,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[medium] test-coverage

Confirmed against the real is_org_bot() guard in reusable-dispatch.yml/dispatch.yml: the actual guard clause is the compound condition [[ -n "${want_role}" && "${role}" != "${want_role}" ]] && continue, but wantRoleGuard only anchors the second half ("${role}" != "${want_role}"). Mutating -n to -z in the two production YAML copies (not dispatch-route-helpers.sh, which is separately covered by dispatch-route-helpers-test.sh's executable tests) would not be caught by any test in this suite, and inverts behavior in both call shapes:

Suggested fix: extend wantRoleGuard to anchor the full compound condition, e.g. `-n "${want_role}" && "${role}" != "${want_role}"`, across all three sources.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

# both exact logins are inlined here directly.
gh api "repos/${SOURCE_REPO}/pulls/${PR_NUM}/reviews" \
--jq "[.[] | select(.state == \"CHANGES_REQUESTED\" and .user.login == \"${REVIEW_BOT}\")] | last | .body // \"\"" \
--jq "[.[] | select(.state == \"CHANGES_REQUESTED\" and (.user.login == \"fullsend-ai-review[bot]\" or .user.login == \"${ORG_NAME}-review[bot]\"))] | last | .body // \"\"" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] prompt-injection

Pre-existing: the Pre-fetch review body steps match review comments by .user.login string, which on the self-managed org path could be spoofed by a third party squatting the GitHub App slug. This path feeds potentially attacker-authored text into the fix agent prompt. Tracked as #5463 and marked out of scope for #5188/#2636. pre-fetch-prior-review.sh already performs performed_via_github_app.client_id provenance validation downstream.

--jq '.role_name' 2>"${api_err}") || {
echo "::warning::Permission API call failed for ${username}: $(cat "${api_err}")" >&2
sanitized_err=$(tr '\n' ' ' < "${api_err}" | sed 's/::/: /g')
echo "::warning::Permission API call failed for ${username}: ${sanitized_err}" >&2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] workflow-command-injection

Pre-existing (improved): ${username} is interpolated unsanitized into ::warning:: workflow commands in has_repo_permission. GitHub login names are restricted to [a-zA-Z0-9-] plus [bot], preventing injection in practice. The PR improves the situation by adding sed sanitization to the API error output.

is_issue_author() {
[[ "${COMMENT_USER_LOGIN:-}" == "${ISSUE_USER_LOGIN:-}" ]]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

Pre-existing: when both COMMENT_USER_LOGIN and ISSUE_USER_LOGIN are unset or empty, is_issue_author returns true because empty-string equality evaluates to true in bash [[ ]]. Not practically reachable.

@ggallen

ggallen commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

Closing this in favor of #5551, which fixes #5188 via label re-triggering instead of actor-identity recognition.

Summary of the outcome: @ifireball's review here was right — this PR's is_org_bot() approach reintroduced a design (recognizing bots by name for dispatch authorization) that #2669 had already evaluated and rejected in favor of label-based gating, a decision #2679 already implemented and shipped for the retro→triage handoff (#2636, which needs no further work). Extending that identity-based approach further, as this PR did, was a step backward from established, working policy — not forward.

The actual root cause of #5188 (review not re-dispatching after a fix-agent push) turned out to be simpler than this PR assumed: post-fix.sh never re-applied the ready-for-review label the way post-code.sh already does for the PR-open case, so the fix-agent's push had no authorization-free path to re-trigger review. #5551 fixes exactly that, with no identity matching anywhere, no changes needed to the CEL-based dispatch path (it already trusts labels unconditionally), and no forge-specific bot-recognition logic (labels work the same on GitHub and GitLab).

@waynesun09 — the review-body content-attribution bug you found during this PR's review (the ${ORG_NAME}-review[bot]-only assumption in the "Pre-fetch review body" steps and the fix-auto-dispatch gate) is real and independent of the above — it's now tracked separately as #5550, since it's a content-attribution question rather than a dispatch-authorization one, and #5551 doesn't touch it.

Thanks both for catching this before it merged.

@ggallen ggallen closed this Jul 23, 2026
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jul 23, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 10:07 PM UTC · Completed 10:26 PM UTC
Commit: 1ba11e9 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #5415 — recognize org bot actors in dispatch routing

Outcome: Closed without merging after 2 days, 17 force-pushes, 18 review agent runs, and 5 human review-squad rounds. A human reviewer (ifireball) identified a fundamental design conflict that no agent caught: the PR's identity-based bot recognition approach (is_org_bot()) conflicted with the established label-based gating pattern described in ADR 0054 and decided in #2669. Replaced by PR #5551 (label-based approach, 103 additions vs 674, 2 files vs 11).

Cascading failure from wrong triage recommendation

The failure cascaded through the full pipeline:

  1. Triage agent (run 29727947850) recommended identity-based bot recognition — an approach that conflicts with ADR 0054's accepted label-based pattern
  2. Code agent failed twice (run 29830392328 — ShellCheck lint failure; run 29832509615workflows permission rejection) attempting to implement the triage agent's recommended approach
  3. Human author (ggallen) implemented the triage agent's recommendation manually as PR fix(#5188): recognize org bot actors in dispatch routing #5415
  4. Review agent ran 18 times, finding implementation bugs (empty-string guards, test breakage, divergence between inline copies) and security micro-findings (authorization-bypass glob, prompt injection) — but never questioned the fundamental design approach
  5. Human reviewer (ifireball) caught the design conflict in a single review, noting the label-based pattern was the established approach

Evidence for existing open issues

This retro provides concrete, high-impact evidence for several existing proposals. No new proposals are warranted — the improvement areas are already well-identified:

  • #1546 (triage agent should validate proposals against design decisions): The triage agent recommended identity-based recognition without checking that ADR 0054 explicitly describes label-based gating as the bot-to-bot dispatch mechanism. ADR 0054 was updated just 3 days before the triage run.
  • #2718 (triage agent should discover existing infrastructure patterns): The label-based gating pattern was already implemented for retro-to-triage handoffs (PR fix(dispatch): add label-based handoffs for bot-to-bot dispatch paths #2679). The triage agent didn't discover this existing pattern before recommending an alternative mechanism.
  • #3814 / agents #325 (triage should avoid recommending workflow file changes / detect GHA modifications and skip code agent): The fix inherently required modifying .github/workflows/reusable-dispatch.yml, which the coder token can't push (workflows:write is intentionally excluded from the coder role). The triage agent labeled the issue ready-to-code anyway.
  • #2812 (review agent should verify implementation against ADR rejected alternatives): The PR reintroduced identity-based bot recognition, which was evaluated and rejected in Adopt label-based gating for agent dispatch and trusted-process identification #2669 in favor of label-based gating. The review agent's intent-coherence sub-agent (which reads ADRs referenced by changed files) did not flag this conflict despite the PR directly modifying ADR 0054.
  • #1194 (review agent should question necessity, not just correctness): Across 18 runs the review agent audited implementation quality — naming consistency, injection vectors, test coverage — without ever asking whether the entire identity-recognition approach was necessary given the existing label-based alternative.
  • #2816 (review finding dedup umbrella): The protected-path finding was repeated identically across all 17 re-review runs. Pre-existing prompt-injection and workflow-command-injection findings appeared in 11 and 14 runs respectively.

What went well

  • The review agent caught real implementation bugs in early runs (CI-breaking test assertion, missing empty-string guards, divergence between helper files), which the author fixed promptly.
  • The code agent's post-code advisory correctly surfaced the workflows permission failure with clear guidance.
  • ifireball's design-level review was decisive and efficient — one review that redirected the entire effort to the correct approach.
  • The replacement PR fix(#5188): re-trigger review via label after fix-agent push #5551 is dramatically simpler, validating ifireball's judgment.

ggallen added a commit to ggallen/fullsend that referenced this pull request Jul 24, 2026
pull_request_target.synchronize fires when the fix agent pushes, but
its actor-identity authorization check is gated on PR_USER_LOGIN — the
PR's original author, which for agent-authored PRs is the code agent's
bot account regardless of who triggered this fix run. GitHub App bots
have no collaborator role, so that check always fails closed and
review is never re-dispatched after a fix-agent push (fullsend-ai#5188).

post-fix.sh now removes then re-adds the ready-for-review label after
a successful push, forcing a fresh labeled webhook event. That path
has no actor-authorization gate at all — label application itself
already requires write access, so it needs no separate identity
check — mirroring post-code.sh's identical handling of the PR-open
case. GitHub does not fire a new labeled event when a label already
present is re-added, hence the remove-then-add sequence.

This supersedes fullsend-ai#5415, which attempted the same fix via an
actor-identity-recognition function (is_org_bot()) extended across
several dispatch-authorization gates. That approach was closed after
review: it reintroduced a design (recognizing bots by name for
dispatch authorization) that issue fullsend-ai#2669 had already evaluated and
rejected in favor of label-based gating — "actors can be spoofed and
the approach is fragile across workflow changes" — a decision PR
fullsend-ai#2679 already implemented and shipped for the retro-to-triage handoff
(closing fullsend-ai#2636). The label-based fix here needs no changes to the
CEL-based dispatch path (internal/harnessdispatch), which already
trusts label-added events unconditionally, and needs no
forge-specific bot-identity logic, since labels work identically
across GitHub and GitLab.

A separate, unrelated bug that fullsend-ai#5415 also touched — the fix agent's
own review-body content-attribution lookups missing the shared
fullsend-ai-review[bot] identity — is tracked independently as fullsend-ai#5550,
since it is not a dispatch-authorization question and is unaffected
by this change.

Signed-off-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug component/ci CI pipelines and checks component/dispatch Workflow dispatch and triggers requires-manual-review Review requires human judgment testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Review agent skipped on bot-authored PRs due to collaborator permission check failing for GitHub App accounts

3 participants