Skip to content

feat(triage): add prerequisites action for upstream issue creation (#401)#2197

Open
ralphbean wants to merge 12 commits into
mainfrom
rbean/401-triage-decompose-issues
Open

feat(triage): add prerequisites action for upstream issue creation (#401)#2197
ralphbean wants to merge 12 commits into
mainfrom
rbean/401-triage-decompose-issues

Conversation

@ralphbean

Copy link
Copy Markdown
Member

Summary

  • Replace the triage agent's blocked action with a new prerequisites action that can both reference existing blockers and create new upstream issues
  • Add create_issues.allow_targets config (orgs + repos allowlist) to control where agents can file issues, with sensible install-time defaults
  • Degraded path: when a target repo isn't in the allowlist, the agent's draft issue body is included in a collapsed <details> block for manual filing

Motivation

The triage agent could detect that an issue was blocked by work elsewhere, but couldn't create the missing tracking issue when none existed. This forced humans to manually identify, draft, and file prerequisite issues in upstream repos. A GitHub App token scoped to one repo can create issues in any public repo (confirmed by GitHub as known behavior), so no auth changes were needed.

Changes

Component Change
internal/config/config.go CreateIssuesConfig + AllowTargets types on both OrgConfig and PerRepoConfig, with install-time defaults and validation
schemas/triage-result.schema.json blockedprerequisites action with existing[] and create[] arrays
agents/triage.md New prerequisites action docs, hard constraint against sufficient when prerequisites exist
scripts/post-triage.sh Allowlist validation, gh issue create for permitted targets, collapsed draft for disallowed/failed creates
docs/agents/triage.md User-facing docs for create_issues config surface

Scope

This is one of three decomposition strategies identified during design. The other two (split muddled issues, parent/child decompose) are future work.

Test plan

  • All Go tests pass (make go-test)
  • Lint passes (make lint)
  • Vet passes (make go-vet)
  • JSON schema validates (jq empty)
  • Post-script syntax check (bash -n)
  • Manual test with a real triage run against a test issue with cross-repo dependencies

Closes #401

🤖 Generated with Claude Code

Design for a new `prerequisites` triage action that replaces `blocked`.
The agent can now express both existing blockers and new issues that need
to be created upstream before progress can happen. Includes allowlist
configuration for cross-repo issue creation and a degraded path when
targets are not authorized.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Seven-task plan covering config structs, JSON schema, agent prompt,
post-script, user docs, and caller updates. TDD approach with exact
file paths and code blocks.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Add CreateIssuesConfig and AllowTargets types to both OrgConfig and
PerRepoConfig. NewOrgConfig populates defaults with the org and
fullsend-ai/fullsend. NewPerRepoConfig populates with the target repo
and fullsend-ai/fullsend.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…ues (#401)

Pass org name and target repo to config constructors so create_issues
defaults are populated at install time.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Replace the blocked action and blocked_by field with a prerequisites
action containing existing[] and create[] arrays. At least one array
must be non-empty.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…pt (#401)

The triage agent can now recommend creating upstream issues via the
prerequisites action's create array, in addition to referencing existing
blockers. Adds hard constraint against emitting sufficient when
prerequisites exist.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Update triage agent docs to explain the new prerequisites action and the
create_issues.allow_targets configuration surface.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Replace the blocked handler with prerequisites. The post-script reads
the create_issues allowlist from config.yaml, creates permitted upstream
issues via gh, and includes collapsed draft bodies for disallowed or
failed creates so humans can file them manually.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

Site preview

Preview: https://33c98cb6-site.fullsend-ai.workers.dev

Commit: e57f10a73ecf1ceb5259b768618aed4cdcec7771

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Review · Started 8:33 PM UTC
Commit: 3a44b0c · View workflow run →

…401)

The agent prompt referenced a nonexistent `prerequisites` label when
checking for prior blockers — the post-script actually applies the
`blocked` label. Also removed unused SOURCE_ORG variable from
post-triage.sh.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:37 PM UTC · Completed 8:51 PM UTC
Commit: 6f79d87 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [breaking-change-schema-contract] internal/scaffold/fullsend-repo/schemas/triage-result.schema.json:12 — The triage result schema replaces blocked with prerequisites and removes blocked_by. Schema, agent prompt, and post-script are embedded in the same scaffold and deploy atomically, so the components stay consistent. However, an in-flight triage agent started under the old schema could emit blocked after the new post-script is deployed, resulting in an unknown action error.
    Remediation: Consider keeping blocked as a recognized action in the post-script for one release cycle, mapping it to the same handler as prerequisites with a deprecation warning.

  • [data_exposure] internal/scaffold/fullsend-repo/scripts/post-triage.sh:171 — AI-generated ISSUE_BODY is posted to external public repositories via gh issue create without human review. A prompt injection in the source issue could instruct the triage agent to include sensitive internal context in the body. The allowlist controls WHERE issues are created but not WHAT is posted.
    Remediation: Consider requiring human approval before creating cross-repo issues (e.g., create as drafts), or add a maxLength constraint on the body field in the JSON schema.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/post-triage.shgh issue create with 2>&1 captures both stdout and stderr into CREATED_URL. On success, if gh emits warnings alongside the URL, CREATED_URL would contain extra lines. Since ALL_URLS is later iterated with for url in ${ALL_URLS}, multi-line output would produce junk entries in the blocker list.
    Remediation: Capture stderr separately or filter the created URL output to only the last line, e.g., CREATED_URL=$(gh issue create ... 2>/dev/null) or pipe through tail -1.

Low

  • [scope-completeness] docs/architecture.md:239 — The triage agent now has write authority to external repos (issue creation), crossing a significant architectural boundary. The PR includes a design spec (docs/superpowers/specs/2026-06-11-triage-prerequisites-design.md) covering the security model, but no formal ADR captures this authority expansion.

  • [scope-authorization] docs/guides/user/bugfix-workflow.md:104 — The user guide mentions the triage agent can create upstream issues and references create_issues.allow_targets, but doesn't cross-link to the detailed allowlist documentation in docs/agents/triage.md.

  • [command_injection] internal/scaffold/fullsend-repo/scripts/post-triage.sh:159::warning:: GHA workflow commands interpolate unsanitized agent-controlled values (TARGET_REPO, CREATED_URL). While ::warning:: cannot set env vars or outputs, attacker-controlled values could inject spurious annotations.

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-triage.sh:143 — When both config file paths are missing, no diagnostic output is emitted. The yq-missing case now correctly emits a ::warning:: (improvement from prior review), but the config-file-missing case silently proceeds with empty allowlists.

  • [naming-inconsistency] internal/scaffold/fullsend-repo/scripts/post-triage.sh — Variable names and comments use old blocker terminology (BLOCKER_LIST, # Merge all blocker URLs, **Blocked by:**) while the action is renamed to prerequisites. The blocked label name is intentionally preserved, but internal variable names and the user-facing heading could align with the new terminology.

  • [architectural-coherence] internal/scaffold/fullsend-repo/scripts/post-triage.sh — Post-script has runtime dependency on yq for reading config.yaml. The code degrades gracefully (warns and disables cross-repo creation) but the dependency is not documented in the execution environment spec.

  • [architectural-coherence] internal/config/config.go — Hardcoded fullsend-ai/fullsend as default in allowlist differs from other config defaults which use empty/nil patterns. This is a deliberate product decision documented in the design spec.

  • [scope-completeness] internal/scaffold/fullsend-repo/scripts/post-triage.shCREATED_URL variable serves double duty (success URL and error message on failure). The control flow is correct but the reuse is confusing.

Info

  • [prior-finding-resolved] internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh — Prior HIGH finding [stale-reference] is resolved. Test cases updated from blocked to prerequisites with new validation tests.

  • [prior-finding-resolved] internal/config/config.go — Prior LOW finding [edge-case] is resolved. validateCreateIssues now uses SplitN with proper empty-part checks. Dedicated test covers /, /repo, owner/, //.

  • [error-message-format] internal/config/config.go — Error messages in validateCreateIssues use slightly different format vs established patterns but are clear and contextual.

  • [edge-case] internal/scaffold/fullsend-repo/schemas/triage-result.schema.jsonanyOf constraint allows non-prerequisites actions to include an extraneous prerequisites field. Benign since the post-script only reads it for the prerequisites action.

  • [naming-inconsistency] docs/agents/triage.md — Documentation for the blocked label mixes prerequisites and blockers terminology in the same sentence. Some mixing is unavoidable since the label name is intentionally blocked.

  • [config-field-addition] internal/config/config.goCreateIssues field addition to OrgConfig/PerRepoConfig is backward compatible (omitempty, nil handled, validated).

Previous run

Review

Findings

High

  • [stale-reference] internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh:95 — Two test cases still reference the removed blocked action and blocked_by property. Lines 95–97 (blocked-missing-blocked-by) and lines 99–101 (blocked-malformed-url) use action:"blocked" with blocked_by, which the schema no longer accepts. After the schema changes, these tests pass accidentally (they expect failure and get failure) but for the wrong reason — blocked is now an invalid action value, not a missing blocked_by field. These are dead tests that no longer validate what they claim to. No replacement tests for the prerequisites action's failure modes exist.
    Remediation: Remove the two stale blocked-* test cases and add equivalents for the prerequisites action: (1) prerequisites-missing-prerequisites-field expecting failure, (2) prerequisites-both-arrays-empty expecting failure, (3) prerequisites-malformed-url in existing expecting failure.

  • [breaking-change-schema-contract] internal/scaffold/fullsend-repo/schemas/triage-result.schema.json:12 — The triage result schema replaces the blocked enum value with prerequisites and removes the blocked_by property, introducing a breaking change to the agent output contract. The schema, agent prompt, and post-script are all scaffolded files in the same repo and are deployed atomically, which mitigates most coordination risk. However, customer repos pinning @v0 will receive this breaking change when the tag is updated.
    Remediation: Ensure all three components (schema, agent prompt agents/triage.md, post-script scripts/post-triage.sh) ship in the same release. Document the breaking change in release notes so operators know to configure create_issues.allow_targets if they want cross-repo issue creation.

Medium

  • [data_exposure] internal/scaffold/fullsend-repo/scripts/post-triage.sh:169 — AI-generated ISSUE_BODY (from the agent's prerequisites.create[].body field) is posted to external public repositories via gh issue create without human review. A prompt injection in the source issue could instruct the triage agent to include sensitive internal context (environment details, internal repo structure) in the body of issues created in public upstream repos. The allowlist limits which repos can be targeted but does not gate the content.
    Remediation: Consider adding content-length limits on the body field in the JSON schema and stripping patterns that look like secrets or internal URLs. Alternatively, create issues as drafts or post the draft body in the source issue comment for human review before creating externally.

  • [incomplete-doc] docs/guides/user/bugfix-workflow.md:105 — Step 2 describes triage as "Checks for blocking dependencies" and "If a blocker is found, it labels blocked" — but the PR adds the capability to create new upstream issues, not just identify existing ones. The existing text is not wrong but is incomplete.
    Remediation: Update to mention that the triage agent can also create prerequisite issues in upstream repos when no tracking issue exists.

  • [incomplete-doc] docs/architecture.md:238 — The triage agent runtime description says "blocking dependency detection (cross-repo)" and "label blocked when progress depends on another open issue or PR" — but triage can now also CREATE new prerequisite issues via the prerequisites action's create array.
    Remediation: Update to mention the issue creation capability controlled by create_issues.allow_targets config.

  • [version-pinning-deployment-coordination] internal/scaffold/fullsend-repo/.github/workflows/triage.yml:29 — Customer repos pin the reusable workflow version via @v0 (a floating major version tag). When the tag is updated to include this release, customers receive the schema change automatically. The only customer action needed is optionally configuring create_issues.allow_targets, and the default (no config = no cross-repo creation) is fail-closed and safe.
    Remediation: Document the upgrade path in release notes: existing installations will automatically gain the prerequisites action but cross-repo issue creation remains disabled until create_issues.allow_targets is configured in config.yaml.

Low

  • [edge-case] internal/config/config.go:1322validateCreateIssues checks !strings.Contains(repo, "/") which allows malformed values like "/", "/repo", or "owner/" to pass validation. These would fail downstream at the GitHub API, but the config validation should catch them.

  • [command_injection] internal/scaffold/fullsend-repo/scripts/post-triage.sh:168CREATED_URL from gh issue create stderr is interpolated unsanitized into a ::warning:: GHA workflow command. The ::warning:: command is informational only (cannot set env vars or outputs), limiting the blast radius to cosmetic injection of additional annotations.

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-triage.sh:166 — No log message when config file is missing or yq is unavailable. The script silently proceeds with empty allowlists, meaning all create entries are rejected without any diagnostic output.

  • [naming-inconsistency] internal/scaffold/fullsend-repo/agents/triage.md:66 — Section 2c heading changed to "Check existing prerequisites" but body text still references "blocker" and "blocking issue." Some references are intentionally correct (the GitHub label IS still called blocked), but the mixed terminology is confusing.

  • [error-message-consistency] internal/config/config.go — Error messages in validateCreateIssues use a hybrid format (create_issues: empty org in allow_targets.orgs) that differs from established patterns (either full dot-path before colon or in with full path).

  • [scope-creep-risk] internal/scaffold/fullsend-repo/agents/triage.md — The agent prompt adds guidance on writing issue bodies "for the target repo's audience" with subjective judgment about back-references. The design spec explicitly discusses this decision, so this is within scope.

  • [implementation-completeness] internal/scaffold/fullsend-repo/scripts/post-triage.sh — Existing issues with blocked label will not be automatically re-evaluated under the new prerequisites action. This gap was pre-existing.

  • [configuration-surface-expansion] docs/agents/triage.md — Org-mode and per-repo-mode defaults differ asymmetrically: org-mode populates allow_targets.orgs, per-repo mode only populates allow_targets.repos. This is architecturally sound but may confuse users migrating between modes.

  • [stale-reference] docs/architecture.md:233 — The label state machine guard description references "blocked" — this refers to the label name which is still correct, but readers might conflate it with the renamed schema action.

  • [cross-repo-authorization-boundary] internal/config/config.go:60 — No pre-validation that allowlisted targets are accessible. The post-script handles inaccessible targets gracefully (error → warning + collapsed draft for manual filing).

  • [schema-constraint-enforcement] internal/scaffold/fullsend-repo/schemas/triage-result.schema.json:91 — The anyOf constraint requiring at least one non-empty array is correct but may produce cryptic validation errors.

  • [backward-compatibility-config-defaults] internal/config/config.go:286 — Existing installations without create_issues config will have cross-repo creation disabled (fail-closed). Safe but may be surprising.

Info

  • [authorization] internal/scaffold/fullsend-repo/scripts/post-triage.sh:148 — Source repo is always implicitly allowed for issue creation even if the admin clears the allowlist. This is documented intentional behavior.

  • [non-breaking-internal-reference] skills/topissues/scripts/topissues.py:132 — The topissues skill's blocked_by refers to GitHub's native GraphQL blockedBy relationship, not the triage schema field. Unaffected by this change.

Previous run (2)

Review

Findings

High

  • [stale-reference] internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh:73 — 6 test cases and 1 expected-output assertion still reference the removed blocked action and blocked_by property. Lines 73–79 (valid-blocked-issue, valid-blocked-pr) use action:"blocked" with blocked_by, which the schema no longer accepts. Lines 95–101 (blocked-missing-blocked-by, blocked-malformed-url) test validation of a now-nonexistent field. Line 291 asserts the allowed-properties list includes blocked_by, but the schema now has prerequisites instead. All 6 tests will fail after the schema changes land.
    Remediation: Replace the blocked test cases with prerequisites equivalents using the new schema shape. Update line 291 to expect prerequisites instead of blocked_by. Add new test cases for the prerequisites conditional (both arrays empty should fail, each non-empty independently should pass).

Medium

  • [command_injection] internal/scaffold/fullsend-repo/scripts/post-triage.sh:168CREATED_URL from the gh issue create error path (captured via 2>&1) is interpolated into a ::warning:: GHA workflow command on the failure path. Error messages from gh are unconstrained and could contain %0A/newline sequences followed by additional workflow commands. TARGET_REPO is constrained by the schema regex enforced before the post-script runs, limiting that vector, but CREATED_URL has no such constraint.
    Remediation: Sanitize CREATED_URL before interpolation into ::warning:: by stripping %0A, %0D, ::, and control characters.

  • [architectural-violation] internal/scaffold/fullsend-repo/scripts/post-triage.sh — The post-script uses yq to read config.yaml but there is no evidence yq is available in the GHA runner environment. The command -v yq fallback silently degrades to empty allowlists, disabling the feature without any log output. Users who expect the install-time defaults to enable cross-repo issue creation would see the feature silently fail.
    Remediation: Either ensure yq is installed in the runner (via a setup step) or emit a ::warning:: when config.yaml exists but yq is unavailable so operators know the feature is degraded.

  • [edge-case] internal/config/config.govalidateCreateIssues checks !strings.Contains(repo, "/") which allows malformed values like "/", "/repo", or "owner/" to pass validation. The design doc states entries must be owner/name format.
    Remediation: Use strings.SplitN(repo, "/", 2) and verify both the owner and name components are non-empty.

  • [data_exposure] internal/scaffold/fullsend-repo/scripts/post-triage.sh:169 — AI-generated ISSUE_BODY is posted to external public repositories without human review. A prompt injection in the source issue could instruct the agent to include sensitive internal context in the body of issues created in public upstream repos. The schema has no maxLength on the body field and there is no approval gate for cross-org creation.
    Remediation: Add a maxLength constraint on the body field in the JSON schema. Consider logging the full body for audit purposes before creation.

  • [naming-inconsistency] internal/scaffold/fullsend-repo/agents/triage.md:66 — Section 2c heading was changed to "Check existing prerequisites" but the body text still references "blocker", "blocked label", and "blocking issue" throughout the section. The label name blocked is correct (it is preserved), but the mixed terminology between the heading and body is confusing.
    Remediation: Align terminology consistently — use "prerequisites" throughout section 2c, keeping the blocked label reference where it refers to the actual GitHub label.

Low

  • [error-message-consistency] internal/config/config.go — Error messages in validateCreateIssues use colon-separated format (create_issues: empty org) while existing validators use dot-notation (status_notifications.comment.start). Minor inconsistency.

  • [incomplete-doc] docs/guides/user/bugfix-workflow.md:105 — Describes triage as only finding and linking existing blockers, not mentioning the new capability to create upstream prerequisite issues. The primary docs (docs/agents/triage.md) were updated in this PR; this is a secondary reference.

  • [incomplete-doc] docs/architecture.md:238 — Architecture overview describes triage as "blocking dependency detection" but does not mention the new issue creation capability.

  • [documentation-clarity] docs/agents/triage.md:42 — Table entry says "The agent identified or created the blockers" but the agent only recommends creation — the post-script performs the actual GitHub API calls.

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-triage.sh:166 — No log message when config file is missing or yq is unavailable, making it hard to debug why prerequisite issue creation was silently skipped.

  • [validation-completeness] internal/config/config.go — Empty string repo gets the "must contain owner/name" error which is slightly misleading. Existing patterns handle different failure modes with distinct messages.

Info

  • [authorization] internal/scaffold/fullsend-repo/scripts/post-triage.sh:148 — Source repo is always implicitly allowed even if the admin clears the allowlist. The docs say "If allow_targets is empty, automatic prerequisite creation is disabled entirely" which is slightly misleading given this exception. The behavior itself is documented elsewhere in the same file.
Previous run

Review

Findings

High

  • [stale-reference] internal/scaffold/fullsend-repo/scripts/post-triage-test.sh:209 — 4 test cases use action:"blocked" and blocked_by in their JSON fixtures (lines 209–225: blocked-posts-comment-and-labels, blocked-applies-blocked-label, blocked-missing-blocked-by-fails, blocked-missing-comment-fails). The PR replaces the blocked) case handler in post-triage.sh with prerequisites), so these tests will hit the unknown-action default and fail. Additionally, several tests for other actions (insufficient, sufficient, duplicate, question) assert that remove_label "blocked" is called — those are still valid since the blocked label persists, but should be verified.
    Remediation: Update the 4 blocked-action test cases to use the new prerequisites action with its prerequisites object structure (existing[] and create[] arrays). Add new test cases for the prerequisites handler: allowlist checking, successful issue creation, disallowed-target fallback with collapsed <details> block, and partial failure handling.

  • [stale-reference] internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh:73 — 4 test cases use action:"blocked" and blocked_by (lines 73–101: valid-blocked-issue, valid-blocked-pr, blocked-missing-blocked-by, blocked-malformed-url). After the schema changes removing blocked from the enum and replacing blocked_by with the prerequisites object, these tests will fail. Line 291 also lists blocked_by in an expected allowed-properties string.
    Remediation: Replace the 4 blocked-action test fixtures with prerequisites fixtures using the new schema shape. Update the allowed-properties assertion at line 291. Add new test cases: valid prerequisites with existing only, valid with create only, both arrays empty (should fail), both non-empty (should pass).

Medium

  • [incomplete-doc] docs/guides/user/bugfix-workflow.md:105 — The bugfix workflow guide describes triage behavior as: "Searches for open issues or PRs...that must be resolved before work can start. If a blocker is found, it labels blocked and posts a comment linking to the blocking issue or PR." This does not mention the new capability to create upstream prerequisite issues when no tracking issue exists. The blocked label name is still correct (the new handler still applies it), but the described behavior no longer covers the full scope of what the prerequisites action does.
    Remediation: Update line 105 to mention that the triage agent can also create prerequisite issues in upstream repos, in addition to linking existing blockers.

Info

  • [sub-agent-failure] The style-conventions review sub-agent did not return findings (missing-context).
Previous run (3)

Review

Findings

High

  • [stale-reference] internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh:73 — 6 test cases and 1 expected-output assertion still reference the removed blocked action and blocked_by property. Lines 73–79 (valid-blocked-issue, valid-blocked-pr) use action:"blocked" with blocked_by, which the schema no longer accepts. Lines 95–101 (blocked-missing-blocked-by, blocked-malformed-url) test validation of a now-nonexistent field. Line 291 asserts the allowed-properties list includes blocked_by, but the schema now has prerequisites instead. All 6 tests will fail after the schema changes land.
    Remediation: Replace the blocked test cases with prerequisites equivalents using the new schema shape. Update line 291 to expect prerequisites instead of blocked_by. Add new test cases for the prerequisites conditional (both arrays empty should fail, each non-empty independently should pass).

Medium

  • [command_injection] internal/scaffold/fullsend-repo/scripts/post-triage.sh:168CREATED_URL from the gh issue create error path (captured via 2>&1) is interpolated into a ::warning:: GHA workflow command on the failure path. Error messages from gh are unconstrained and could contain %0A/newline sequences followed by additional workflow commands. TARGET_REPO is constrained by the schema regex enforced before the post-script runs, limiting that vector, but CREATED_URL has no such constraint.
    Remediation: Sanitize CREATED_URL before interpolation into ::warning:: by stripping %0A, %0D, ::, and control characters.

  • [architectural-violation] internal/scaffold/fullsend-repo/scripts/post-triage.sh — The post-script uses yq to read config.yaml but there is no evidence yq is available in the GHA runner environment. The command -v yq fallback silently degrades to empty allowlists, disabling the feature without any log output. Users who expect the install-time defaults to enable cross-repo issue creation would see the feature silently fail.
    Remediation: Either ensure yq is installed in the runner (via a setup step) or emit a ::warning:: when config.yaml exists but yq is unavailable so operators know the feature is degraded.

  • [edge-case] internal/config/config.govalidateCreateIssues checks !strings.Contains(repo, "/") which allows malformed values like "/", "/repo", or "owner/" to pass validation. The design doc states entries must be owner/name format.
    Remediation: Use strings.SplitN(repo, "/", 2) and verify both the owner and name components are non-empty.

  • [data_exposure] internal/scaffold/fullsend-repo/scripts/post-triage.sh:169 — AI-generated ISSUE_BODY is posted to external public repositories without human review. A prompt injection in the source issue could instruct the agent to include sensitive internal context in the body of issues created in public upstream repos. The schema has no maxLength on the body field and there is no approval gate for cross-org creation.
    Remediation: Add a maxLength constraint on the body field in the JSON schema. Consider logging the full body for audit purposes before creation.

  • [naming-inconsistency] internal/scaffold/fullsend-repo/agents/triage.md:66 — Section 2c heading was changed to "Check existing prerequisites" but the body text still references "blocker", "blocked label", and "blocking issue" throughout the section. The label name blocked is correct (it is preserved), but the mixed terminology between the heading and body is confusing.
    Remediation: Align terminology consistently — use "prerequisites" throughout section 2c, keeping the blocked label reference where it refers to the actual GitHub label.

Low

  • [error-message-consistency] internal/config/config.go — Error messages in validateCreateIssues use colon-separated format (create_issues: empty org) while existing validators use dot-notation (status_notifications.comment.start). Minor inconsistency.

  • [incomplete-doc] docs/guides/user/bugfix-workflow.md:105 — Describes triage as only finding and linking existing blockers, not mentioning the new capability to create upstream prerequisite issues. The primary docs (docs/agents/triage.md) were updated in this PR; this is a secondary reference.

  • [incomplete-doc] docs/architecture.md:238 — Architecture overview describes triage as "blocking dependency detection" but does not mention the new issue creation capability.

  • [documentation-clarity] docs/agents/triage.md:42 — Table entry says "The agent identified or created the blockers" but the agent only recommends creation — the post-script performs the actual GitHub API calls.

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-triage.sh:166 — No log message when config file is missing or yq is unavailable, making it hard to debug why prerequisite issue creation was silently skipped.

  • [validation-completeness] internal/config/config.go — Empty string repo gets the "must contain owner/name" error which is slightly misleading. Existing patterns handle different failure modes with distinct messages.

Info

  • [authorization] internal/scaffold/fullsend-repo/scripts/post-triage.sh:148 — Source repo is always implicitly allowed even if the admin clears the allowlist. The docs say "If allow_targets is empty, automatic prerequisite creation is disabled entirely" which is slightly misleading given this exception. The behavior itself is documented elsewhere in the same file.
Previous run (4)

Review

Findings

High

  • [stale-reference] internal/scaffold/fullsend-repo/scripts/post-triage-test.sh:209 — 4 test cases use action:"blocked" and blocked_by in their JSON fixtures (lines 209–225: blocked-posts-comment-and-labels, blocked-applies-blocked-label, blocked-missing-blocked-by-fails, blocked-missing-comment-fails). The PR replaces the blocked) case handler in post-triage.sh with prerequisites), so these tests will hit the unknown-action default and fail. Additionally, several tests for other actions (insufficient, sufficient, duplicate, question) assert that remove_label "blocked" is called — those are still valid since the blocked label persists, but should be verified.
    Remediation: Update the 4 blocked-action test cases to use the new prerequisites action with its prerequisites object structure (existing[] and create[] arrays). Add new test cases for the prerequisites handler: allowlist checking, successful issue creation, disallowed-target fallback with collapsed <details> block, and partial failure handling.

  • [stale-reference] internal/scaffold/fullsend-repo/scripts/validate-output-schema-test.sh:73 — 4 test cases use action:"blocked" and blocked_by (lines 73–101: valid-blocked-issue, valid-blocked-pr, blocked-missing-blocked-by, blocked-malformed-url). After the schema changes removing blocked from the enum and replacing blocked_by with the prerequisites object, these tests will fail. Line 291 also lists blocked_by in an expected allowed-properties string.
    Remediation: Replace the 4 blocked-action test fixtures with prerequisites fixtures using the new schema shape. Update the allowed-properties assertion at line 291. Add new test cases: valid prerequisites with existing only, valid with create only, both arrays empty (should fail), both non-empty (should pass).

Medium

  • [incomplete-doc] docs/guides/user/bugfix-workflow.md:105 — The bugfix workflow guide describes triage behavior as: "Searches for open issues or PRs...that must be resolved before work can start. If a blocker is found, it labels blocked and posts a comment linking to the blocking issue or PR." This does not mention the new capability to create upstream prerequisite issues when no tracking issue exists. The blocked label name is still correct (the new handler still applies it), but the described behavior no longer covers the full scope of what the prerequisites action does.
    Remediation: Update line 105 to mention that the triage agent can also create prerequisite issues in upstream repos, in addition to linking existing blockers.

Info

  • [sub-agent-failure] The style-conventions review sub-agent did not return findings (missing-context).

@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.

Replace the four blocked-action test cases with five prerequisites-action
test cases that exercise the new schema (existing[], create[], allowlist
validation). Set up GITHUB_WORKSPACE with a config.yaml fixture and add
a mock gh issue-create handler that returns a fake URL.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:24 PM UTC · Completed 9:38 PM UTC
Commit: 080368c · View workflow run →

@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.


# Process create entries: create issues, collect URLs.
CREATE_COUNT=$(jq '.prerequisites.create // [] | length' "${RESULT_FILE}")
CREATED_URLS=""

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] command_injection

CREATED_URL from gh error path (captured via 2>&1) is interpolated into a ::warning:: GHA workflow command. Error messages from gh are unconstrained and could contain %0A/newline sequences. TARGET_REPO is schema-constrained but CREATED_URL has no such constraint.

Suggested fix: Sanitize CREATED_URL before interpolation into ::warning:: by stripping %0A, %0D, ::, and control characters.

# Process create entries: create issues, collect URLs.
CREATE_COUNT=$(jq '.prerequisites.create // [] | length' "${RESULT_FILE}")
CREATED_URLS=""
FAILED_CREATES=""

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] data_exposure

AI-generated ISSUE_BODY posted to external public repos without human review. Prompt injection in source issue could instruct agent to include sensitive internal context. No maxLength on body field in schema.

Suggested fix: Add maxLength to body field in schema. Consider logging the full body for audit purposes before creation.

If a cross-repo search fails or returns an error (e.g., due to access restrictions), note this in your reasoning as an information gap rather than concluding no blocking work exists.

### 2c. Check existing blockers
### 2c. Check existing prerequisites

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] naming-inconsistency

Section 2c heading changed to 'Check existing prerequisites' but body text still references 'blocker', 'blocked label', and 'blocking issue' inconsistently.

Suggested fix: Align terminology consistently to prerequisites throughout section 2c.

@ralphbean

Copy link
Copy Markdown
Member Author

Ran fullsend run triage locally from this branch against appdumpster/test-repo#34 — an issue that explicitly depends on appdumpster/misleading#2.

The agent picked action: "prerequisites" and correctly populated prerequisites.existing with the upstream blocker URL. Schema validation passed.

{
  "action": "prerequisites",
  "prerequisites": {
    "existing": [
      { "url": "https://github.com/appdumpster/misleading/issues/2" }
    ],
    "create": []
  }
}

Also pushed 080368c to fix the test job — post-triage-test.sh still had four blocked-action test cases that needed updating for prerequisites.

Trace: 86c0ca4b-9403-4628-8f45-e48d6eb855ef

@ralphbean

Copy link
Copy Markdown
Member Author

Second run — this time against appdumpster/test-repo#35, an issue that depends on a missing feature in appdumpster/workflows where no upstream issue exists yet.

The agent picked action: "prerequisites" and populated create with a well-scoped issue for the upstream repo:

{
  "action": "prerequisites",
  "prerequisites": {
    "existing": [],
    "create": [
      {
        "repo": "appdumpster/workflows",
        "title": "Add optional SBOM attestation support to sign-artifact.yml",
        "body": "## Problem\n\nThe `sign-artifact.yml` reusable workflow currently supports cosign signing and build provenance attestation, but has no support for SBOM generation or SBOM attestation. ..."
      }
    ]
  }
}

So both paths work — existing (issue #34 run) and create (this one). Schema validation passed on both.

Trace: 8929172c-03ce-45d6-9649-9f01025febc5

@ralphbean

Copy link
Copy Markdown
Member Author

Full end-to-end run against appdumpster/test-repo#35 with the post-script enabled. The agent identified a missing upstream issue in appdumpster/workflows, and the post-script created it: appdumpster/workflows#17. Original issue got blocked label and a comment with the blocker reference.

Trace: 9dac2520-90e3-4fd2-bd1d-000cfee45841

…401)

Replace blocked-action test cases with prerequisites-action equivalents
and update the expected property list (blocked_by → prerequisites).

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Review · Started 1:16 AM UTC
Commit: 11bae49 · View workflow run →

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.17647% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/config/config.go 93.33% 1 Missing and 1 partial ⚠️
internal/cli/admin.go 66.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@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.

"action": {
"type": "string",
"enum": ["insufficient", "duplicate", "sufficient", "blocked", "question"]
"enum": ["insufficient", "duplicate", "sufficient", "prerequisites", "question"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] breaking-change-schema-contract

The triage result schema replaces the blocked enum value with prerequisites and removes the blocked_by property. The schema, agent prompt, and post-script are scaffolded files deployed atomically, but customer repos pinning @v0 receive the breaking change when the tag is updated.

Suggested fix: Ensure all three components ship in the same release. Document the breaking change in release notes so operators know to configure create_issues.allow_targets.

# Process create entries: create issues, collect URLs.
CREATE_COUNT=$(jq '.prerequisites.create // [] | length' "${RESULT_FILE}")
CREATED_URLS=""
FAILED_CREATES=""

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] data_exposure

AI-generated ISSUE_BODY is posted to external public repositories via gh issue create without human review. A prompt injection in the source issue could instruct the triage agent to include sensitive internal context in the body of issues created in public upstream repos.

Suggested fix: Consider adding content-length limits on the body field in the JSON schema and stripping patterns that look like secrets or internal URLs.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:16 AM UTC · Completed 1:34 AM UTC
Commit: 11bae49 · View workflow run →

- Replace stale blocked-* schema validation tests with prerequisites
  equivalents (missing field, both arrays empty, malformed URL)
- Fix validateCreateIssues to reject malformed repo formats like "/",
  "/repo", "owner/"
- Align triage.md section 2c terminology from "blocker" to
  "prerequisite" consistently
- Update bugfix-workflow.md and architecture.md to document upstream
  issue creation capability
- Emit ::warning:: when yq is unavailable so silent degradation of
  cross-repo issue creation is diagnosable

Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 12, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:06 PM UTC · Completed 4:23 PM UTC
Commit: e57f10a · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Triage agent cannot split or decompose issues that contain multiple concerns

2 participants