Skip to content

fix(#847): use closing-reference queries for PR-issue matching - #887

Merged
maruiz93 merged 9 commits into
mainfrom
agent/847-fix-pr-issue-match
Aug 21, 2026
Merged

fix(#847): use closing-reference queries for PR-issue matching#887
maruiz93 merged 9 commits into
mainfrom
agent/847-fix-pr-issue-match

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Summary

  • GitHub: Replace gh pr list --search "N in:body,title" with a GraphQL query using closedByPullRequestsReferences on the Issue type. This returns only PRs with closing keywords (Fixes #N, Closes #N, etc.) targeting the issue — the same approach used by the dispatch-level check in reusable-dispatch.yml.
  • GitLab: Change the jq filter from matching any #N reference to matching only closing keywords (Close/Fix/Resolve/Implement variants) before the issue reference. Case-insensitive matching with word boundaries prevents false positives.
  • Tests: Update the pre-code-test.sh mock to handle gh api graphql and use GraphQL response format for test data.

Root cause

The pre-script's forge_list_prs_for_issue used gh pr list --search "${ISSUE_NUMBER} in:body,title" — a raw text search that matched any PR containing the issue number as a substring. This caused false positives:

  • Issue #1 matched PR #13 (titled docs(#12): ...) because 1 appears in #12
  • Issue #6358 matched PR #6355 because #6355 mentioned #6358 in a "Related" section

Testing

  • All 27 pre-code-test.sh tests pass (source mode)
  • All post-code-test.sh tests pass
  • make check-bundle confirms bundled scripts are in sync
  • Secret scan passed on all changed files

Closes #847

Post-script verification

  • Branch is not main/master (agent/847-fix-pr-issue-match)
  • Secret scan passed (gitleaks — c981f987117a78816483c96d5256b302b1b1cfca..HEAD)
  • PR body secret scan passed (gitleaks — no-git)

@fullsend-ai-coder
fullsend-ai-coder Bot requested a review from a team as a code owner August 20, 2026 10:56
@fullsend-ai-coder fullsend-ai-coder Bot added the ready-for-review Triggers review agent dispatch label Aug 20, 2026
@maruiz93

Copy link
Copy Markdown
Contributor

/fs-fix Add unit tests in scripts/pre-code-test.sh for the following gaps:

  1. False-positive regression tests (the exact scenarios from issue Code agent pre-script PR check matches PRs for unrelated issues #847):

  2. Closed PR filtering: A GraphQL response containing a PR with "state": "MERGED" or "state": "CLOSED" must not block — only "state": "OPEN" PRs count.

  3. Positive closing-keyword match: A PR with Fixes #42 or Closes #42 in its body MUST still block (confirm the happy path works with the new GraphQL format).

All tests should use the existing _gql_wrap helper and run_test_stdout / run_test harness already in pre-code-test.sh. No functional test changes needed — unit tests are the right layer here.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 11:43 AM UTC · Completed 11:49 AM UTC

Commit: e163c95 · View workflow run →

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (human-triggered)

Added 6 unit tests covering all 3 requested gaps: false-positive regression (3 tests for issue #847 scenarios), closed/merged PR filtering (2 tests for MERGED and CLOSED states), and positive closing-keyword match (1 happy-path test). All 33 tests pass.

Fixed (3):

  1. Missing false-positive regression tests for issue Code agent pre-script PR check matches PRs for unrelated issues #847 scenarios (scripts/pre-code-test.sh): Added 3 tests: title-substring (harness/triage.yaml has stale /tmp/workspace/ dest paths; env vars never reach sandbox #1 not blocked by PR mentioning feat: extract code, fix, review, retro, prioritize agents from fullsend #12), related-without-closing-keyword (chore(ci): expand pinact pre-commit hook to cover all workflow files #42 not blocked by 'Related: chore(ci): expand pinact pre-commit hook to cover all workflow files #42'), and different-issue-substring (harness/triage.yaml has stale /tmp/workspace/ dest paths; env vars never reach sandbox #1 not blocked by PR mentioning feat(agents): add scribe agent #10). Uses EMPTY_GQL_JSON since closedByPullRequestsReferences would not return these PRs.
  2. Missing closed/merged PR filtering tests (scripts/pre-code-test.sh): Added 2 tests: merged-pr-does-not-block and closed-pr-does-not-block. New MERGED_PR_JSON and CLOSED_PR_JSON fixtures use _gql_wrap with state MERGED/CLOSED to verify the jq .state == 'OPEN' filter correctly excludes them.
  3. Missing positive closing-keyword match happy-path test (scripts/pre-code-test.sh): Added closing-ref-open-pr-still-blocks test confirming an OPEN human PR returned by closedByPullRequestsReferences (representing Fixes chore(ci): expand pinact pre-commit hook to cover all workflow files #42 or Closes chore(ci): expand pinact pre-commit hook to cover all workflow files #42) correctly blocks the agent.

Tests: passed

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

fullsend-ai-coder Bot added a commit that referenced this pull request Aug 20, 2026
Add 6 new tests to pre-code-test.sh covering review-requested gaps:

- False-positive regression (issue #847): verify issue #1 is not blocked
  by a PR referencing #12 in its title, issue #42 is not blocked by a
  "Related: #42" mention without a closing keyword, and issue #1 is not
  blocked by a PR mentioning #10 (substring false positive).
- Closed/merged PR filtering: verify PRs with state MERGED or CLOSED are
  filtered out by the jq .state == "OPEN" selector.
- Positive closing-keyword match: verify an OPEN human PR returned by
  closedByPullRequestsReferences still blocks (happy path).

Addresses review feedback on #887
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 20, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:51 AM UTC · Completed 12:27 PM UTC

Commit: d0b9d71 · View workflow run →

@maruiz93

Copy link
Copy Markdown
Contributor

Note: The dispatch-level PR check in .fullsend's dispatch.yml has the same false-positive bug this PR fixes in the pre-script. It still uses gh pr list --search "${ISSUE_NUMBER} in:title,body" (bare substring match). Tracked in fullsend-ai/fullsend#5900.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [protected-path] scripts/*, skills/* — All 6 changed files fall under protected paths (scripts/, skills/). Changed files: scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/post-code.sh, scripts/pre-code.sh, scripts/pre-code-test.sh, skills/github-forge/SKILL.md. The PR links to issue Code agent pre-script PR check matches PRs for unrelated issues #847 and explains the rationale for the change. Human approval is always required for protected-path changes, regardless of context.

Low

  • [naming-consistency] scripts/lib/github-code-ops.lib.shforge_list_prs_for_issue() now uses --arg for jq parameter passing while sibling forge_list_prs_for_branch() uses shell interpolation in --jq. This is a pre-existing idiom difference not introduced by this PR. No change required per AGENTS.md rule 3 (surgical changes).
Previous run

Review

Findings

Medium

  • [protected-path] scripts/, skills/ — All 6 changed files are under protected paths (scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/post-code.sh, scripts/pre-code.sh, scripts/pre-code-test.sh, skills/github-forge/SKILL.md). The PR links to issue Code agent pre-script PR check matches PRs for unrelated issues #847 and explains the rationale for modifying these infrastructure scripts. Human approval is always required for protected-path changes, regardless of context.

  • [shell-injection-in-jq] scripts/lib/github-code-ops.lib.sh:115 — The --jq argument interpolates ${bot_login} and ${coder_bot_login} via shell double quotes directly in the jq expression. Unlike the GitLab implementation, which correctly uses jq's --arg mechanism ($bot1, $bot2), the GitHub implementation embeds these values through shell interpolation. The values are currently hardcoded constants (fullsend-ai[bot], fullsend-ai-coder[bot]), so this is a latent defense-in-depth gap rather than an active vulnerability. Pre-existing issue carried forward from the old code.
    Remediation: Use --arg bot_login "${bot_login}" --arg coder_bot_login "${coder_bot_login}" flags and reference $bot_login/$coder_bot_login in the jq expression, matching the GitLab implementation's approach.

Low

  • [edge-case] scripts/lib/gitlab-code-ops.lib.sh:471 — The GitLab closing-keyword regex only matches comma-separated multi-issue references (e.g., Fixes #1, #42). Space-separated (Fixes #1 #42) and and-joined (Fixes #1 and #42) closing references are not matched, though both are valid closing patterns per GitLab documentation. The GitHub path is unaffected (uses authoritative closedByPullRequestsReferences API). Impact is fail-open: the bot may proceed to create a competing PR when a human PR with a space-separated closing reference already exists.

  • [regex-injection] scripts/lib/gitlab-code-ops.lib.sh:470 — The $issue_number variable is concatenated into a jq test() regex via --arg. If $issue_number contained regex metacharacters, the regex could behave unexpectedly. Mitigated by upstream validation (^[1-9][0-9]*$) ensuring only digit strings reach this code.

  • [naming-consistency] scripts/pre-code-test.sh:39 — Stale comment # Write the pr list output to a file so the mock can read it. still references the old "pr list" concept after the variable was renamed from pr_list_output to graphql_output and the file from pr-list-output.txt to graphql-output.txt on the adjacent line.
    Remediation: Update to # Write the GraphQL output to a file so the mock can read it.

Previous run (2)

Review

Findings

Medium

  • [protected-path] scripts/*, skills/github-forge/SKILL.md — All 6 changed files are under protected paths (scripts/, skills/). The PR links to issue Code agent pre-script PR check matches PRs for unrelated issues #847 and explains the rationale for modifying these governance/infrastructure files, but human approval is always required for protected-path changes regardless of context.

  • [stale-reference] scripts/pre-code-test.sh:27 — The build_mock() docstring still describes its parameter as "JSON array to return for gh pr list calls" but the mock was updated to handle gh api graphql calls. The local variable pr_list_output (line 31) is also stale.
    Remediation: Update the docstring to reference gh api graphql and the GraphQL response envelope format. Consider renaming pr_list_output to match its new purpose.

Low

  • [edge-case] scripts/lib/github-code-ops.lib.sh:93 — The -F number flag passes issue_number as a GraphQL Int. A non-numeric value would cause a silent failure (fail-open via 2>/dev/null || true). In practice, the caller extracts issue_number from a validated URL via forge_parse_issue_url, so non-numeric input cannot reach this path in normal operation.

  • [edge-case] scripts/lib/github-code-ops.lib.sh:98closedByPullRequestsReferences(first: 50) caps results at 50 linked PRs per issue with no pagination cursor handling. This is a theoretical limit — 50+ linked open non-bot PRs per issue is extremely unlikely.

  • [behavioral-change] scripts/lib/github-code-ops.lib.sh:87 — The migration from text search to closedByPullRequestsReferences narrows detection to PRs with closing keywords only. PRs referencing an issue without closing keywords (e.g., "Related to chore(ci): expand pinact pre-commit hook to cover all workflow files #42") will no longer be detected. This is the explicit intent of issue Code agent pre-script PR check matches PRs for unrelated issues #847 and aligns with the dispatch-level check in reusable-dispatch.yml.

Previous run (3)

Review

Findings

Medium

  • [protected-path] scripts/*, skills/* — All 6 changed files fall under protected paths (scripts/, skills/). The PR links to issue Code agent pre-script PR check matches PRs for unrelated issues #847 and explains the rationale for modifying these infrastructure scripts. Human approval is always required for protected-path changes, regardless of context.

  • [documentation comment] scripts/pre-code-test.sh:25 — The build_mock function docstring still references the old behavior: says $1 is a "JSON array to return for gh pr list calls" and "Pass an empty string for no PRs." After this PR, the mock routes gh api graphql, the argument is a full GraphQL response envelope, and callers pass EMPTY_GQL_JSON for the no-PRs case.
    Remediation: Update the docstring to describe the new GraphQL response format and EMPTY_GQL_JSON convention.

Low

  • [fail-open] scripts/lib/github-code-ops.lib.sh:119 — The GitHub forge_list_prs_for_issue uses || true to suppress GraphQL API failures, causing the duplicate-PR check to be silently skipped when the API is unreachable. Not a regression (pre-existing pattern), but the migration to GraphQL does not improve the fail-open posture. The GitLab counterpart properly fails closed on first-page API failure.

  • [scope-creep] scripts/lib/gitlab-code-ops.lib.sh:188 — The GitLab closing-keyword regex omits implement/implements/implemented/implementing, which GitLab documents as valid closing keywords. This could cause false negatives when a user writes "Implements #N" as a closing reference.

Previous run (4)

Review

Findings

Medium

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:186 — The closing-keyword regex requires the keyword to immediately precede #N (with only optional whitespace and repo qualifier). GitLab supports comma-separated closing references (e.g., Closes #41, #42), where the keyword applies to all listed issues. With issue_number=42, the regex fails to match Closes #41, #42 because #42 is preceded by , rather than a closing keyword. This creates a false-negative path where a legitimate closing MR is not detected, potentially causing duplicate PRs. The GitHub path handles this natively via closedByPullRequestsReferences.
    Remediation: Extend the regex to also match #<issue_number> when preceded by a comma separator following an initial closing keyword (e.g., add an alternative (?:,\s*)#<issue_number>).

  • [protected-path] scripts/ — All 5 changed files are under scripts/, a protected path. The PR links to issue Code agent pre-script PR check matches PRs for unrelated issues #847 and explains the rationale for the change. Human approval is always required for protected-path changes, regardless of context.

Low

  • [logic-error] scripts/lib/gitlab-code-ops.lib.sh:186 — The regex includes implement(?:s|ed|ing)? as a closing keyword, but GitLab's standard closing pattern only recognizes close/fix/resolve variants. Implements #42 does not close issue chore(ci): expand pinact pre-commit hook to cover all workflow files #42 on gitlab.com — including it creates a false-positive path where informational MR mentions block the agent.

  • [stale-comment] scripts/lib/gitlab-code-ops.lib.sh:160 — The block comment at lines 160–163 still describes the old search strategy ("filter by body/title containing #<IID> with word boundaries"). The filter now uses closing-keyword matching. The same stale comment appears in the bundled copies in pre-code.sh and post-code.sh.

  • [regex-injection] scripts/lib/gitlab-code-ops.lib.sh:183 — The $issue_number jq variable is concatenated into regex patterns in test() calls. If issue_number contained regex metacharacters, they would be interpreted as regex. The current caller validates ISSUE_NUMBER as digits-only via URL validation, so this is not exploitable, but the function itself does not enforce the constraint.

  • [stale-example] skills/github-forge/SKILL.md:32 — The Pull Requests section documents gh pr list --search "ISSUE_NUMBER in:body,title" for PR-issue matching. This PR replaces that approach with closedByPullRequestsReferences because the text search produces false positives. The SKILL.md still presents the old pattern for issue-number lookups.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (5)

Review

Findings

Medium

  • [protected-path] scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/pre-code.sh, scripts/post-code.sh, scripts/pre-code-test.sh — This PR modifies files under protected paths (scripts/). The PR links to issue Code agent pre-script PR check matches PRs for unrelated issues #847 and explains the rationale for the change. Human approval is always required for protected-path changes, regardless of context.

Low

  • [naming-consistency] scripts/lib/gitlab-code-ops.lib.sh — The parameter is renamed from search_term to issue_number for interface parity with the GitHub counterpart, but within the GitLab function body the value is still used as a text-search term for jq regex matching (--arg term "${issue_number}"). Consider adding a brief comment noting the issue number is used as a search term in the GitLab implementation, or renaming the internal jq --arg alias.

  • [injection] scripts/lib/github-code-ops.lib.sh:97 — The GraphQL query interpolates ${issue_number} directly into the query string without using a GraphQL variable: issue(number: ${issue_number}). Upstream validation (^[1-9][0-9]*$) ensures only positive integers reach this point, so the practical risk is low. For defense-in-depth, consider using gh api graphql -F number:="${issue_number}" with GraphQL variables instead of shell interpolation.

Previous run (6)

Review

Findings

Medium

  • [edge-case] scripts/lib/gitlab-code-ops.lib.sh:186 — The GitLab closing-keyword regex uses \s+ between the keyword and the issue reference, requiring whitespace only. GitLab's default closing pattern includes an optional colon ((:?)), making both Closes #42 and Closes: #42 valid closing references. The regex will not match MRs using the colon form (e.g., Fixes: #42, Closes: #42), which could cause the pre-script to miss a blocking MR and allow duplicate work.
    Remediation: Change \s+ to :?\s+ in both regex instances (title and description) to match the optional colon that GitLab's default closing pattern supports.

  • [protected-path] scripts/lib/github-code-ops.lib.sh, scripts/lib/gitlab-code-ops.lib.sh, scripts/post-code.sh, scripts/pre-code.sh, scripts/pre-code-test.sh — All 5 changed files are under the scripts/ protected path. Human approval is always required for protected-path changes, regardless of context. The PR links to issue Code agent pre-script PR check matches PRs for unrelated issues #847 and the description explains the rationale for modifying these governance/infrastructure files.

Low

  • [test-inadequate] scripts/pre-code-test.sh:585 — The three fp-issue847-* regression tests pass EMPTY_GQL_JSON as the mock response. Since the GitHub fix uses server-side filtering via closedByPullRequestsReferences, these tests only verify the empty-response path and document intent rather than exercising the false-positive elimination. The broader test suite does cover positive paths (e.g., closing-ref-open-pr-still-blocks, merged-pr-does-not-block).

  • [naming-consistency] scripts/lib/gitlab-code-ops.lib.sh:157 — The first parameter of forge_list_prs_for_issue() is named search_term in the GitLab implementation but was renamed to issue_number in the GitHub implementation. The caller always passes ${ISSUE_NUMBER}. The GitHub rename correctly reflects the new semantics; the GitLab side retains the old name.
    Remediation: Rename local search_term="$1" to local issue_number="$1" in gitlab-code-ops.lib.sh and update the --arg term reference accordingly.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 12:29 PM UTC · Completed 12:34 PM UTC

Commit: d0b9d71 · View workflow run →

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 2 (bot-triggered)

Addressed 2 of 3 review findings. Fixed the GitLab closing-keyword regex to support optional colon (matching GitLab's default closing pattern) and renamed search_term to issue_number for consistency with the GitHub implementation. Disagreed with the test-inadequate finding — the fp-issue847-* tests correctly test the empty-response path since filtering happens server-side. All 32 pre-code tests and all post-code tests pass.

Fixed (2):

  1. GitLab closing-keyword regex missing optional colon support (scripts/lib/gitlab-code-ops.lib.sh): Changed \s+ to :?\s+ in both title and description regex patterns to match GitLab's default closing pattern which supports an optional colon (e.g., 'Closes: chore(ci): expand pinact pre-commit hook to cover all workflow files #42', 'Fixes: chore(ci): expand pinact pre-commit hook to cover all workflow files #42'). Change propagated to bundled scripts via make script-build.
  2. Naming inconsistency: search_term vs issue_number parameter (scripts/lib/gitlab-code-ops.lib.sh): Renamed local search_term to issue_number in the GitLab forge_list_prs_for_issue() and updated the --arg term reference to use the new variable name, matching the GitHub implementation's naming convention. Change propagated to bundled scripts via make script-build.

Disagreed (1):

  1. fp-issue847- regression tests only verify empty-response path*: The reviewer rated this low and acknowledged that the broader test suite covers positive paths (e.g., closing-ref-open-pr-still-blocks, merged-pr-does-not-block). The fp-issue847-* tests intentionally use EMPTY_GQL_JSON because the GitHub GraphQL API (closedByPullRequestsReferences) performs server-side filtering — the false-positive scenarios would never return results. These tests document intent and verify the script correctly handles the empty-response path. Adding mock positive responses to these specific tests would not increase confidence since the filtering happens server-side, not in our jq.

Tests: passed

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

fullsend-ai-coder Bot added a commit that referenced this pull request Aug 20, 2026
- Add :? to the GitLab closing-keyword regex to match GitLab's default
  closing pattern which allows an optional colon (e.g., "Closes: #42")
- Rename search_term to issue_number in the GitLab forge_list_prs_for_issue()
  to match the GitHub implementation's naming convention

Addresses review feedback on #887
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 20, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:36 PM UTC · Completed 12:55 PM UTC

Commit: a993169 · View workflow run →

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review August 20, 2026 12:55

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Aug 20, 2026
@maruiz93

Copy link
Copy Markdown
Contributor

/fs-fix Address the two remaining low-severity review findings:

  1. [injection] scripts/lib/github-code-ops.lib.sh:97 — Use GraphQL variables instead of shell interpolation in the gh api graphql call. Pass owner, name, and issue_number via -F owner="${owner}" -F name="${name}" -F number:="${issue_number}" and reference them as $owner, $name, $number in the query string. Propagate to bundled scripts via make script-build.

  2. [naming-consistency] scripts/lib/gitlab-code-ops.lib.sh — The jq --arg term "${issue_number}" alias is confusing now that the shell variable was renamed from search_term to issue_number. Rename the jq arg from term to issue_number (i.e., --arg issue_number "${issue_number}") and update the jq references accordingly. Propagate to bundled scripts via make script-build.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 2:29 PM UTC · Completed 2:36 PM UTC

Commit: a993169 · View workflow run →

fullsend-ai-coder Bot added a commit that referenced this pull request Aug 20, 2026
- Use -F flags to pass owner, name, and issue_number as proper GraphQL
  variables instead of shell-interpolating them into the query string,
  eliminating an injection vector in the gh api graphql call.
- Rename jq --arg alias from "term" to "issue_number" in the GitLab
  forge_list_prs_for_issue to match the renamed shell variable.
- Propagate both changes to bundled scripts via make script-build.

Addresses review feedback on #887
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 3 (human-triggered)

Addressed both remaining low-severity review findings: (1) replaced shell interpolation with proper GraphQL variables in the GitHub forge_list_prs_for_issue function, and (2) renamed the jq --arg alias from 'term' to 'issue_number' in the GitLab forge_list_prs_for_issue function. Changes propagated to pre-code.sh and post-code.sh via make script-build. All 32 pre-code tests pass.

Fixed (2):

  1. injection: use GraphQL variables instead of shell interpolation in gh api graphql call (scripts/lib/github-code-ops.lib.sh): Replaced shell interpolation of owner, name, and issue_number in the GraphQL query string with proper GraphQL variables passed via -F flags (-F owner, -F name, -F number:=). Query now uses query($owner: String!, $name: String!, $number: Int!) with variable references. Single-quoted query string eliminates need for shell expansion. Propagated to bundled scripts via make script-build.
  2. naming-consistency: rename jq --arg alias from term to issue_number (scripts/lib/gitlab-code-ops.lib.sh): Renamed jq --arg from 'term' to 'issue_number' to match the renamed shell variable. Updated all three jq references from $term to $issue_number in the closing-keyword filter and agent-branch exclusion. Propagated to bundled scripts via make script-build.

Tests: passed

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 20, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:38 PM UTC · Completed 2:57 PM UTC

Commit: cfdff7b · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Aug 20, 2026
@fullsend-ai-coder

fullsend-ai-coder Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 2:59 PM UTC · Completed 3:08 PM UTC

Commit: cfdff7b · View workflow run →

fullsend-ai-coder Bot added a commit that referenced this pull request Aug 20, 2026
…yword

- Extend GitLab closing-keyword regex to match comma-separated issue
  lists (e.g., "Closes #41, #42") by adding a repeating group for
  preceding issue refs separated by commas
- Remove implement(?:s|ed|ing)? from keyword list — GitLab only
  recognizes close/fix/resolve variants as closing keywords
- Update stale block comment describing the old search strategy
- Update SKILL.md to replace stale gh pr list --search pattern with
  closedByPullRequestsReferences GraphQL query

Addresses review feedback on #887
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 4 (bot-triggered)

Addressed 4 of 6 review findings with code fixes. Fixed the GitLab closing-keyword regex to handle comma-separated issue lists and removed the invalid 'implement' keyword. Updated stale comments and SKILL.md documentation. Disagreed with 2 findings: regex-injection (caller already validates digits-only) and protected-path (informational, requires human approval).

Fixed (4):

  1. Comma-separated closing references not matched (scripts/lib/gitlab-code-ops.lib.sh): Extended the closing-keyword regex to handle comma-separated issue lists (e.g., 'Closes fix: fall back to workspace scripts dir for script companions #41, chore(ci): expand pinact pre-commit hook to cover all workflow files #42') by adding a repeating group (?:(?:[a-zA-Z0-9._/-]+)?#\d+(?:\s*,\s*))* before the target issue number. This allows the regex to match any issue in a comma-separated closing reference list via backtracking.
  2. implement keyword is not a GitLab closing keyword (scripts/lib/gitlab-code-ops.lib.sh): Removed implement(?:s|ed|ing)? from the closing-keyword regex. GitLab only recognizes close/fix/resolve variants as closing patterns. Including 'implement' created a false-positive path where informational MR mentions could block the agent.
  3. Stale comment describing old search strategy (scripts/lib/gitlab-code-ops.lib.sh): Updated the block comment at lines 160-163 to describe the current closing-keyword filtering approach instead of the old word-boundary text search. Same fix propagated to bundled copies in pre-code.sh and post-code.sh via make script-build.
  4. Stale SKILL.md documents old gh pr list --search pattern (skills/github-forge/SKILL.md): Replaced the stale 'gh pr list --search ISSUE_NUMBER in:body,title' example with a general keyword search and the closedByPullRequestsReferences GraphQL query pattern, matching the approach now used by forge_list_prs_for_issue.

Disagreed (2):

  1. regex-injection via $issue_number in jq test() calls: The caller validates ISSUE_NUMBER as digits-only via URL validation before reaching this function. Adding redundant numeric validation inside the function would violate the AGENTS.md principle of not adding error handling for scenarios that cannot occur. The function's contract assumes a valid issue number from its caller.
  2. Protected-path: all 5 changed files are under scripts/: Acknowledged. All changes are under protected paths (scripts/ and skills/). Human approval is required per policy — this is informational and does not require a code change from the fix agent.

Tests: passed

Next steps:

  • /fs-review — request a re-review of the changes
  • /fs-fix <your instruction> — run another fix pass with specific guidance
  • Push commits directly — review re-runs automatically on push
    Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 21, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 8:29 AM UTC · Ended 8:43 AM UTC

Commit: d188633 · View workflow run →

Replace the text-search approach in forge_list_prs_for_issue with
semantically correct closing-reference queries on both forges:

GitHub: Use the closedByPullRequestsReferences GraphQL field on the
Issue type, which returns only PRs with closing keywords (Fixes #N,
Closes #N, etc.) targeting the issue. This matches the approach already
used by the dispatch-level check in reusable-dispatch.yml.

GitLab: Change the jq filter from matching any #N reference to matching
only closing keywords (Close/Fix/Resolve/Implement variants) before the
issue reference. Case-insensitive matching with word boundaries prevents
both false positives from unrelated mentions and substring collisions.

The previous text-search approach (gh pr list --search "N in:body,title")
matched any PR containing the issue number as a substring, causing false
positives: issue #1 matched PR #13 (titled "docs(#12): ...") and issue
#6358 matched PR #6355 (which mentioned #6358 in a "Related" section).

Update pre-code-test.sh mock to handle gh api graphql and use GraphQL
response format for test data.

Note: pre-commit could not run in sandbox (network restriction). The
post-script runs an authoritative pre-commit check on the runner.

Closes #847
Add 6 new tests to pre-code-test.sh covering review-requested gaps:

- False-positive regression (issue #847): verify issue #1 is not blocked
  by a PR referencing #12 in its title, issue #42 is not blocked by a
  "Related: #42" mention without a closing keyword, and issue #1 is not
  blocked by a PR mentioning #10 (substring false positive).
- Closed/merged PR filtering: verify PRs with state MERGED or CLOSED are
  filtered out by the jq .state == "OPEN" selector.
- Positive closing-keyword match: verify an OPEN human PR returned by
  closedByPullRequestsReferences still blocks (happy path).

Addresses review feedback on #887
- Add :? to the GitLab closing-keyword regex to match GitLab's default
  closing pattern which allows an optional colon (e.g., "Closes: #42")
- Rename search_term to issue_number in the GitLab forge_list_prs_for_issue()
  to match the GitHub implementation's naming convention

Addresses review feedback on #887
- Use -F flags to pass owner, name, and issue_number as proper GraphQL
  variables instead of shell-interpolating them into the query string,
  eliminating an injection vector in the gh api graphql call.
- Rename jq --arg alias from "term" to "issue_number" in the GitLab
  forge_list_prs_for_issue to match the renamed shell variable.
- Propagate both changes to bundled scripts via make script-build.

Addresses review feedback on #887
…yword

- Extend GitLab closing-keyword regex to match comma-separated issue
  lists (e.g., "Closes #41, #42") by adding a repeating group for
  preceding issue refs separated by commas
- Remove implement(?:s|ed|ing)? from keyword list — GitLab only
  recognizes close/fix/resolve variants as closing keywords
- Update stale block comment describing the old search strategy
- Update SKILL.md to replace stale gh pr list --search pattern with
  closedByPullRequestsReferences GraphQL query

Addresses review feedback on #887
Use -f (string) for owner/name and -F without := for number.
The := raw-JSON prefix with -F passes null for integer variables
in gh api graphql; -F without := auto-detects the type correctly.

Addresses review feedback on #887
Update the build_mock() docstring in pre-code-test.sh to reference
"gh api graphql" instead of the stale "gh pr list", and rename the
pr_list_output variable to graphql_output throughout the file to
match the current mock implementation.

Addresses review feedback on #887
@maruiz93
maruiz93 force-pushed the agent/847-fix-pr-issue-match branch from d188633 to e9eff25 Compare August 21, 2026 08:43
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 21, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:45 AM UTC · Completed 9:02 AM UTC

Commit: e9eff25 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

Pass bot_login and coder_bot_login via jq --arg flags instead of shell
interpolation for defense-in-depth consistency with the GitLab
implementation. Update the test mock to forward --arg bindings to jq.

Signed-off-by: Marta Anon <manon@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 21, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 9:11 AM UTC · Ended 9:11 AM UTC

Commit: 4227516 · View workflow run →

@maruiz93
maruiz93 force-pushed the agent/847-fix-pr-issue-match branch from 4227516 to a7d0645 Compare August 21, 2026 09:11
Extend the closing-keyword regex separator to match space-separated
(Closes #41 #42) and and-joined (Closes #41 and #42) issue lists in
addition to comma-separated ones.

Signed-off-by: Marta Anon <manon@redhat.com>
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Ended 9:13 AM UTC

Commit: a7d0645 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 21, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 9:15 AM UTC · Ended 9:33 AM UTC

Commit: b84db77 · View workflow run →

@maruiz93

Copy link
Copy Markdown
Contributor

Addressed review 5 findings:

  • shell-injection-in-jq (github-code-ops.lib.sh:115): Addressed in a7d0645 — switched to jq --arg flags for bot login filtering, matching the GitLab implementation's approach.
  • naming-consistency (pre-code-test.sh:39): Addressed in a7d0645 — updated stale comment to reference GraphQL output.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:15 AM UTC · Completed 9:33 AM UTC

Commit: b84db77 · View workflow run →

@maruiz93
maruiz93 added this pull request to the merge queue Aug 21, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 21, 2026
@maruiz93
maruiz93 added this pull request to the merge queue Aug 21, 2026
Merged via the queue into main with commit e5d4591 Aug 21, 2026
82 checks passed
@maruiz93
maruiz93 deleted the agent/847-fix-pr-issue-match branch August 21, 2026 10:45
@fullsend-ai-retro

fullsend-ai-retro Bot commented Aug 21, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 10:47 AM UTC · Completed 10:59 AM UTC

Commit: b84db77 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #887 — fix(#847): use closing-reference queries for PR-issue matching

PR #887 fixed a false-positive bug in forge_list_prs_for_issue across GitHub (migrated to closedByPullRequestsReferences GraphQL query) and GitLab (replaced bare #N regex with closing-keyword-aware matching). The workflow spanned ~24 hours: 1 triage run, 1 code agent run, 6 fix iterations (3 human-triggered, 3 bot-triggered), 7 review passes, and 2 direct human commits.

What went well:

  • Triage agent correctly identified the root cause and recommended the GraphQL approach, which the code agent followed.
  • Review agent caught genuine issues across iterations: GraphQL injection risk, GitLab regex edge cases (optional colon, comma-separated refs, invalid implement keyword), shell-injection-in-jq, and stale documentation.
  • Code agent's disagree decisions were well-reasoned (server-side filtering makes client-side test moot; upstream digit validation mitigates regex injection).
  • Human reviewer (maruiz93) provided precise, actionable /fs-fix instructions throughout.

Key improvement area: The code agent's initial PR omitted regression tests despite the code-implementation skill mandating "every behavioral change must have a corresponding test change" and despite the triage output recommending specific false-positive test scenarios. This forced an extra /fs-fix iteration to add them.

Evidence for existing open issues:

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human Agent loop needs human intervention ok-to-test ready-for-review Triggers review agent dispatch requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Code agent pre-script PR check matches PRs for unrelated issues

2 participants