From b008766c05e5ee21a555175e1809c058e0c33968 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:30:00 +0000 Subject: [PATCH 1/4] Initial plan From 3dd75b314a7cfee65f37b6282f74c29d619d6b53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:39:15 +0000 Subject: [PATCH 2/4] Implement strict slash command matching with startsWith and exact equality Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/archie.lock.yml | 22 +++++----- .github/workflows/brave.lock.yml | 7 ++-- .github/workflows/cloclo.lock.yml | 39 ++++++++++-------- .github/workflows/craft.lock.yml | 6 ++- .github/workflows/grumpy-reviewer.lock.yml | 11 ++--- .github/workflows/mergefest.lock.yml | 7 ++-- .github/workflows/pdf-summary.lock.yml | 14 ++++--- .github/workflows/plan.lock.yml | 10 +++-- .github/workflows/poem-bot.lock.yml | 7 ++-- .../workflows/pr-nitpick-reviewer.lock.yml | 36 ++++++++-------- .github/workflows/q.lock.yml | 36 ++++++++-------- .github/workflows/scout.lock.yml | 41 ++++++++++--------- .github/workflows/security-review.lock.yml | 12 +++--- .github/workflows/tidy.lock.yml | 7 ++-- .github/workflows/unbloat-docs.lock.yml | 8 ++-- pkg/workflow/command.go | 22 +++++++++- pkg/workflow/command_precision_test.go | 36 ++++++++++------ pkg/workflow/command_test.go | 22 ++++++---- pkg/workflow/compiler_events_test.go | 8 ++-- 19 files changed, 201 insertions(+), 150 deletions(-) diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 030f3aa22a..f7bf7287ce 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -55,10 +55,12 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issues') && (contains(github.event.issue.body, '/archie')) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/archie')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/archie')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request') && (contains(github.event.pull_request.body, '/archie'))) + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/archie ')) || + (github.event.issue.body == '/archie')) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/archie ')) || + (github.event.comment.body == '/archie')) && (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && + (((startsWith(github.event.comment.body, '/archie ')) || (github.event.comment.body == '/archie')) && + (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request') && ((startsWith(github.event.pull_request.body, '/archie ')) || + (github.event.pull_request.body == '/archie'))) runs-on: ubuntu-slim permissions: contents: read @@ -1030,13 +1032,11 @@ jobs: pre_activation: if: > - (github.event_name == 'issues') && (contains(github.event.issue.body, '/archie')) || - (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/archie')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/archie')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request') && - (contains(github.event.pull_request.body, '/archie')) + (github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/archie ')) || (github.event.issue.body == '/archie')) || + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/archie ')) || (github.event.comment.body == '/archie')) && + (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/archie ')) || + (github.event.comment.body == '/archie')) && (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request') && + ((startsWith(github.event.pull_request.body, '/archie ')) || (github.event.pull_request.body == '/archie')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 244e56fc78..7022629bc3 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -46,8 +46,8 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/brave')) && - (github.event.issue.pull_request == null))) + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/brave ')) || + (github.event.comment.body == '/brave')) && (github.event.issue.pull_request == null))) runs-on: ubuntu-slim permissions: contents: read @@ -1024,7 +1024,8 @@ jobs: pre_activation: if: > - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/brave')) && (github.event.issue.pull_request == null)) + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/brave ')) || (github.event.comment.body == '/brave')) && + (github.event.issue.pull_request == null)) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 1fee7c9d3c..11416542d0 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -70,14 +70,17 @@ jobs: if: > (needs.pre_activation.outputs.activated == 'true') && ((((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || - github.event_name == 'discussion_comment') && ((github.event_name == 'issues') && (contains(github.event.issue.body, '/cloclo')) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/cloclo')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/cloclo')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request_review_comment') && (contains(github.event.comment.body, '/cloclo')) || - (github.event_name == 'pull_request') && (contains(github.event.pull_request.body, '/cloclo')) || - (github.event_name == 'discussion') && - (contains(github.event.discussion.body, '/cloclo')) || (github.event_name == 'discussion_comment') && - (contains(github.event.comment.body, '/cloclo')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || + github.event_name == 'discussion_comment') && ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/cloclo ')) || + (github.event.issue.body == '/cloclo')) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/cloclo ')) || + (github.event.comment.body == '/cloclo')) && (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && + (((startsWith(github.event.comment.body, '/cloclo ')) || (github.event.comment.body == '/cloclo')) && + (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && + ((startsWith(github.event.comment.body, '/cloclo ')) || (github.event.comment.body == '/cloclo')) || + (github.event_name == 'pull_request') && + ((startsWith(github.event.pull_request.body, '/cloclo ')) || (github.event.pull_request.body == '/cloclo')) || + (github.event_name == 'discussion') && ((startsWith(github.event.discussion.body, '/cloclo ')) || (github.event.discussion.body == '/cloclo')) || + (github.event_name == 'discussion_comment') && ((startsWith(github.event.comment.body, '/cloclo ')) || + (github.event.comment.body == '/cloclo')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment'))) && ((github.event_name != 'issues') || ((github.event.action != 'labeled') || (github.event.label.name == 'cloclo')))) @@ -1378,16 +1381,16 @@ jobs: if: > (((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment') && - ((github.event_name == 'issues') && (contains(github.event.issue.body, '/cloclo')) || (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/cloclo')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/cloclo')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request_review_comment') && - (contains(github.event.comment.body, '/cloclo')) || (github.event_name == 'pull_request') && - (contains(github.event.pull_request.body, '/cloclo')) || - (github.event_name == 'discussion') && (contains(github.event.discussion.body, '/cloclo')) || - (github.event_name == 'discussion_comment') && - (contains(github.event.comment.body, '/cloclo')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || + ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/cloclo ')) || (github.event.issue.body == '/cloclo')) || + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/cloclo ')) || (github.event.comment.body == '/cloclo')) && + (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/cloclo ')) || + (github.event.comment.body == '/cloclo')) && (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && + ((startsWith(github.event.comment.body, '/cloclo ')) || (github.event.comment.body == '/cloclo')) || + (github.event_name == 'pull_request') && + ((startsWith(github.event.pull_request.body, '/cloclo ')) || (github.event.pull_request.body == '/cloclo')) || + (github.event_name == 'discussion') && ((startsWith(github.event.discussion.body, '/cloclo ')) || (github.event.discussion.body == '/cloclo')) || + (github.event_name == 'discussion_comment') && ((startsWith(github.event.comment.body, '/cloclo ')) || + (github.event.comment.body == '/cloclo')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment'))) && ((github.event_name != 'issues') || ((github.event.action != 'labeled') || (github.event.label.name == 'cloclo'))) diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index d38d73f7b7..9bee965c1a 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -46,7 +46,8 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issues') && (contains(github.event.issue.body, '/craft'))) + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/craft ')) || + (github.event.issue.body == '/craft'))) runs-on: ubuntu-slim permissions: contents: read @@ -1057,7 +1058,8 @@ jobs: if-no-files-found: ignore pre_activation: - if: (github.event_name == 'issues') && (contains(github.event.issue.body, '/craft')) + if: > + (github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/craft ')) || (github.event.issue.body == '/craft')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/grumpy-reviewer.lock.yml b/.github/workflows/grumpy-reviewer.lock.yml index a2623539ca..b16b732c96 100644 --- a/.github/workflows/grumpy-reviewer.lock.yml +++ b/.github/workflows/grumpy-reviewer.lock.yml @@ -49,9 +49,9 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/grumpy')) && - (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && - (contains(github.event.comment.body, '/grumpy'))) + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/grumpy ')) || + (github.event.comment.body == '/grumpy')) && (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && + ((startsWith(github.event.comment.body, '/grumpy ')) || (github.event.comment.body == '/grumpy'))) runs-on: ubuntu-slim permissions: contents: read @@ -1106,8 +1106,9 @@ jobs: pre_activation: if: > - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/grumpy')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request_review_comment') && (contains(github.event.comment.body, '/grumpy')) + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/grumpy ')) || (github.event.comment.body == '/grumpy')) && + (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && + ((startsWith(github.event.comment.body, '/grumpy ')) || (github.event.comment.body == '/grumpy')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index 4199f90cb5..1ce91ab0ee 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -45,8 +45,8 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/mergefest')) && - (github.event.issue.pull_request != null))) + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/mergefest ')) || + (github.event.comment.body == '/mergefest')) && (github.event.issue.pull_request != null))) runs-on: ubuntu-slim permissions: contents: read @@ -1044,7 +1044,8 @@ jobs: pre_activation: if: > - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/mergefest')) && (github.event.issue.pull_request != null)) + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/mergefest ')) || + (github.event.comment.body == '/mergefest')) && (github.event.issue.pull_request != null)) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index bbf2b0f03b..2016db6015 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -63,9 +63,10 @@ jobs: needs: pre_activation if: > (needs.pre_activation.outputs.activated == 'true') && (((github.event_name == 'issue_comment' || github.event_name == 'issues') && - ((github.event_name == 'issues') && (contains(github.event.issue.body, '/summarize')) || (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/summarize')) && (github.event.issue.pull_request == null)))) || - (!(github.event_name == 'issue_comment' || github.event_name == 'issues'))) + ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/summarize ')) || (github.event.issue.body == '/summarize')) || + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/summarize ')) || + (github.event.comment.body == '/summarize')) && (github.event.issue.pull_request == null)))) || (!(github.event_name == 'issue_comment' || + github.event_name == 'issues'))) runs-on: ubuntu-slim permissions: contents: read @@ -1124,9 +1125,10 @@ jobs: pre_activation: if: > ((github.event_name == 'issue_comment' || github.event_name == 'issues') && ((github.event_name == 'issues') && - (contains(github.event.issue.body, '/summarize')) || (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/summarize')) && - (github.event.issue.pull_request == null)))) || (!(github.event_name == 'issue_comment' || github.event_name == 'issues')) + ((startsWith(github.event.issue.body, '/summarize ')) || (github.event.issue.body == '/summarize')) || + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/summarize ')) || + (github.event.comment.body == '/summarize')) && (github.event.issue.pull_request == null)))) || (!(github.event_name == 'issue_comment' || + github.event_name == 'issues')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 1234da984f..d5e86dfa9c 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -49,8 +49,9 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/plan')) && - (github.event.issue.pull_request == null)) || (github.event_name == 'discussion_comment') && (contains(github.event.comment.body, '/plan'))) + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/plan ')) || + (github.event.comment.body == '/plan')) && (github.event.issue.pull_request == null)) || (github.event_name == 'discussion_comment') && + ((startsWith(github.event.comment.body, '/plan ')) || (github.event.comment.body == '/plan'))) runs-on: ubuntu-slim permissions: contents: read @@ -1102,8 +1103,9 @@ jobs: pre_activation: if: > - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/plan')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'discussion_comment') && (contains(github.event.comment.body, '/plan')) + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/plan ')) || (github.event.comment.body == '/plan')) && + (github.event.issue.pull_request == null)) || (github.event_name == 'discussion_comment') && ((startsWith(github.event.comment.body, '/plan ')) || + (github.event.comment.body == '/plan')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 092713452f..2bf17d4f4f 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -54,7 +54,8 @@ jobs: needs: pre_activation if: > (needs.pre_activation.outputs.activated == 'true') && (((github.event_name == 'issues') && ((github.event_name == 'issues') && - (contains(github.event.issue.body, '/poem-bot')))) || (!(github.event_name == 'issues'))) + ((startsWith(github.event.issue.body, '/poem-bot ')) || (github.event.issue.body == '/poem-bot')))) || + (!(github.event_name == 'issues'))) runs-on: ubuntu-slim permissions: contents: read @@ -1657,8 +1658,8 @@ jobs: pre_activation: if: > - ((github.event_name == 'issues') && ((github.event_name == 'issues') && (contains(github.event.issue.body, '/poem-bot')))) || - (!(github.event_name == 'issues')) + ((github.event_name == 'issues') && ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/poem-bot ')) || + (github.event.issue.body == '/poem-bot')))) || (!(github.event_name == 'issues')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/pr-nitpick-reviewer.lock.yml b/.github/workflows/pr-nitpick-reviewer.lock.yml index 93b22bd7e5..567380b51f 100644 --- a/.github/workflows/pr-nitpick-reviewer.lock.yml +++ b/.github/workflows/pr-nitpick-reviewer.lock.yml @@ -68,14 +68,15 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issues') && (contains(github.event.issue.body, '/nit')) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/nit')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/nit')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request_review_comment') && (contains(github.event.comment.body, '/nit')) || - (github.event_name == 'pull_request') && (contains(github.event.pull_request.body, '/nit')) || - (github.event_name == 'discussion') && - (contains(github.event.discussion.body, '/nit')) || (github.event_name == 'discussion_comment') && - (contains(github.event.comment.body, '/nit'))) + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/nit ')) || + (github.event.issue.body == '/nit')) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/nit ')) || + (github.event.comment.body == '/nit')) && (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && + (((startsWith(github.event.comment.body, '/nit ')) || (github.event.comment.body == '/nit')) && (github.event.issue.pull_request != null)) || + (github.event_name == 'pull_request_review_comment') && ((startsWith(github.event.comment.body, '/nit ')) || + (github.event.comment.body == '/nit')) || (github.event_name == 'pull_request') && ((startsWith(github.event.pull_request.body, '/nit ')) || + (github.event.pull_request.body == '/nit')) || (github.event_name == 'discussion') && ((startsWith(github.event.discussion.body, '/nit ')) || + (github.event.discussion.body == '/nit')) || (github.event_name == 'discussion_comment') && ((startsWith(github.event.comment.body, '/nit ')) || + (github.event.comment.body == '/nit'))) runs-on: ubuntu-slim permissions: contents: read @@ -1179,16 +1180,15 @@ jobs: pre_activation: if: > - (github.event_name == 'issues') && (contains(github.event.issue.body, '/nit')) || (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/nit')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/nit')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request_review_comment') && - (contains(github.event.comment.body, '/nit')) || (github.event_name == 'pull_request') && - (contains(github.event.pull_request.body, '/nit')) || - (github.event_name == 'discussion') && (contains(github.event.discussion.body, '/nit')) || - (github.event_name == 'discussion_comment') && - (contains(github.event.comment.body, '/nit')) + (github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/nit ')) || (github.event.issue.body == '/nit')) || + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/nit ')) || (github.event.comment.body == '/nit')) && + (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/nit ')) || + (github.event.comment.body == '/nit')) && (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && + ((startsWith(github.event.comment.body, '/nit ')) || (github.event.comment.body == '/nit')) || + (github.event_name == 'pull_request') && + ((startsWith(github.event.pull_request.body, '/nit ')) || (github.event.pull_request.body == '/nit')) || + (github.event_name == 'discussion') && ((startsWith(github.event.discussion.body, '/nit ')) || (github.event.discussion.body == '/nit')) || + (github.event_name == 'discussion_comment') && ((startsWith(github.event.comment.body, '/nit ')) || (github.event.comment.body == '/nit')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 958b4bc31b..7d21300e02 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -67,14 +67,15 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issues') && (contains(github.event.issue.body, '/q')) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/q')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/q')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request_review_comment') && (contains(github.event.comment.body, '/q')) || - (github.event_name == 'pull_request') && (contains(github.event.pull_request.body, '/q')) || - (github.event_name == 'discussion') && - (contains(github.event.discussion.body, '/q')) || (github.event_name == 'discussion_comment') && - (contains(github.event.comment.body, '/q'))) + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/q ')) || + (github.event.issue.body == '/q')) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/q ')) || + (github.event.comment.body == '/q')) && (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && + (((startsWith(github.event.comment.body, '/q ')) || (github.event.comment.body == '/q')) && (github.event.issue.pull_request != null)) || + (github.event_name == 'pull_request_review_comment') && ((startsWith(github.event.comment.body, '/q ')) || + (github.event.comment.body == '/q')) || (github.event_name == 'pull_request') && ((startsWith(github.event.pull_request.body, '/q ')) || + (github.event.pull_request.body == '/q')) || (github.event_name == 'discussion') && ((startsWith(github.event.discussion.body, '/q ')) || + (github.event.discussion.body == '/q')) || (github.event_name == 'discussion_comment') && ((startsWith(github.event.comment.body, '/q ')) || + (github.event.comment.body == '/q'))) runs-on: ubuntu-slim permissions: contents: read @@ -1214,16 +1215,17 @@ jobs: pre_activation: if: > - (github.event_name == 'issues') && (contains(github.event.issue.body, '/q')) || (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/q')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/q')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request_review_comment') && - (contains(github.event.comment.body, '/q')) || (github.event_name == 'pull_request') && - (contains(github.event.pull_request.body, '/q')) || - (github.event_name == 'discussion') && (contains(github.event.discussion.body, '/q')) || + (github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/q ')) || (github.event.issue.body == '/q')) || + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/q ')) || (github.event.comment.body == '/q')) && + (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/q ')) || + (github.event.comment.body == '/q')) && (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && + ((startsWith(github.event.comment.body, '/q ')) || (github.event.comment.body == '/q')) || + (github.event_name == 'pull_request') && + ((startsWith(github.event.pull_request.body, '/q ')) || (github.event.pull_request.body == '/q')) || + (github.event_name == 'discussion') && + ((startsWith(github.event.discussion.body, '/q ')) || (github.event.discussion.body == '/q')) || (github.event_name == 'discussion_comment') && - (contains(github.event.comment.body, '/q')) + ((startsWith(github.event.comment.body, '/q ')) || (github.event.comment.body == '/q')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 0510481447..4f999da41c 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -81,16 +81,17 @@ jobs: if: > (needs.pre_activation.outputs.activated == 'true') && (((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || - github.event_name == 'discussion_comment') && ((github.event_name == 'issues') && (contains(github.event.issue.body, '/scout')) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/scout')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/scout')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request_review_comment') && (contains(github.event.comment.body, '/scout')) || - (github.event_name == 'pull_request') && (contains(github.event.pull_request.body, '/scout')) || - (github.event_name == 'discussion') && - (contains(github.event.discussion.body, '/scout')) || (github.event_name == 'discussion_comment') && - (contains(github.event.comment.body, '/scout')))) || - (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || - github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment'))) + github.event_name == 'discussion_comment') && ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/scout ')) || + (github.event.issue.body == '/scout')) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/scout ')) || + (github.event.comment.body == '/scout')) && (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && + (((startsWith(github.event.comment.body, '/scout ')) || (github.event.comment.body == '/scout')) && (github.event.issue.pull_request != null)) || + (github.event_name == 'pull_request_review_comment') && ((startsWith(github.event.comment.body, '/scout ')) || + (github.event.comment.body == '/scout')) || (github.event_name == 'pull_request') && ((startsWith(github.event.pull_request.body, '/scout ')) || + (github.event.pull_request.body == '/scout')) || (github.event_name == 'discussion') && ((startsWith(github.event.discussion.body, '/scout ')) || + (github.event.discussion.body == '/scout')) || (github.event_name == 'discussion_comment') && ((startsWith(github.event.comment.body, '/scout ')) || + (github.event.comment.body == '/scout')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || + github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || + github.event_name == 'discussion_comment'))) runs-on: ubuntu-slim permissions: contents: read @@ -1220,16 +1221,16 @@ jobs: if: > ((github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment') && - ((github.event_name == 'issues') && (contains(github.event.issue.body, '/scout')) || (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/scout')) && (github.event.issue.pull_request == null)) || - (github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/scout')) && (github.event.issue.pull_request != null)) || - (github.event_name == 'pull_request_review_comment') && - (contains(github.event.comment.body, '/scout')) || (github.event_name == 'pull_request') && - (contains(github.event.pull_request.body, '/scout')) || - (github.event_name == 'discussion') && (contains(github.event.discussion.body, '/scout')) || - (github.event_name == 'discussion_comment') && - (contains(github.event.comment.body, '/scout')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || + ((github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/scout ')) || (github.event.issue.body == '/scout')) || + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/scout ')) || (github.event.comment.body == '/scout')) && + (github.event.issue.pull_request == null)) || (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/scout ')) || + (github.event.comment.body == '/scout')) && (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && + ((startsWith(github.event.comment.body, '/scout ')) || (github.event.comment.body == '/scout')) || + (github.event_name == 'pull_request') && + ((startsWith(github.event.pull_request.body, '/scout ')) || (github.event.pull_request.body == '/scout')) || + (github.event_name == 'discussion') && ((startsWith(github.event.discussion.body, '/scout ')) || (github.event.discussion.body == '/scout')) || + (github.event_name == 'discussion_comment') && ((startsWith(github.event.comment.body, '/scout ')) || + (github.event.comment.body == '/scout')))) || (!(github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment')) runs-on: ubuntu-slim diff --git a/.github/workflows/security-review.lock.yml b/.github/workflows/security-review.lock.yml index 639e3bf25b..ac760a4634 100644 --- a/.github/workflows/security-review.lock.yml +++ b/.github/workflows/security-review.lock.yml @@ -49,9 +49,9 @@ jobs: activation: needs: pre_activation if: > - (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/security-review')) && - (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && - (contains(github.event.comment.body, '/security-review'))) + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/security-review ')) || + (github.event.comment.body == '/security-review')) && (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && + ((startsWith(github.event.comment.body, '/security-review ')) || (github.event.comment.body == '/security-review'))) runs-on: ubuntu-slim permissions: contents: read @@ -1175,9 +1175,9 @@ jobs: pre_activation: if: > - (github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/security-review')) && - (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && - (contains(github.event.comment.body, '/security-review')) + (github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/security-review ')) || + (github.event.comment.body == '/security-review')) && (github.event.issue.pull_request != null)) || (github.event_name == 'pull_request_review_comment') && + ((startsWith(github.event.comment.body, '/security-review ')) || (github.event.comment.body == '/security-review')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index d40c5abd21..0b1c9da4b9 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -58,7 +58,8 @@ jobs: needs: pre_activation if: > (needs.pre_activation.outputs.activated == 'true') && (((github.event_name == 'issue_comment') && ((github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/tidy')) && (github.event.issue.pull_request != null)))) || (!(github.event_name == 'issue_comment'))) + (((startsWith(github.event.comment.body, '/tidy ')) || (github.event.comment.body == '/tidy')) && (github.event.issue.pull_request != null)))) || + (!(github.event_name == 'issue_comment'))) runs-on: ubuntu-slim permissions: contents: read @@ -1128,8 +1129,8 @@ jobs: pre_activation: if: > - ((github.event_name == 'issue_comment') && ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/tidy')) && - (github.event.issue.pull_request != null)))) || (!(github.event_name == 'issue_comment')) + ((github.event_name == 'issue_comment') && ((github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/tidy ')) || + (github.event.comment.body == '/tidy')) && (github.event.issue.pull_request != null)))) || (!(github.event_name == 'issue_comment')) runs-on: ubuntu-slim permissions: contents: read diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 442e3bac3f..5dec37736a 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -51,8 +51,8 @@ jobs: needs: pre_activation if: > (needs.pre_activation.outputs.activated == 'true') && (((github.event_name == 'issue_comment') && ((github.event_name == 'issue_comment') && - ((contains(github.event.comment.body, '/unbloat')) && (github.event.issue.pull_request != null)))) || - (!(github.event_name == 'issue_comment'))) + (((startsWith(github.event.comment.body, '/unbloat ')) || (github.event.comment.body == '/unbloat')) && + (github.event.issue.pull_request != null)))) || (!(github.event_name == 'issue_comment'))) runs-on: ubuntu-slim permissions: contents: read @@ -1302,8 +1302,8 @@ jobs: pre_activation: if: > - ((github.event_name == 'issue_comment') && ((github.event_name == 'issue_comment') && ((contains(github.event.comment.body, '/unbloat')) && - (github.event.issue.pull_request != null)))) || (!(github.event_name == 'issue_comment')) + ((github.event_name == 'issue_comment') && ((github.event_name == 'issue_comment') && (((startsWith(github.event.comment.body, '/unbloat ')) || + (github.event.comment.body == '/unbloat')) && (github.event.issue.pull_request != null)))) || (!(github.event_name == 'issue_comment')) runs-on: ubuntu-slim permissions: contents: read diff --git a/pkg/workflow/command.go b/pkg/workflow/command.go index 19540620c8..ee7f2db893 100644 --- a/pkg/workflow/command.go +++ b/pkg/workflow/command.go @@ -38,14 +38,32 @@ func buildEventAwareCommandCondition(commandNames []string, commandEvents []stri hasDiscussionComment := slices.Contains(eventNames, "discussion_comment") // Helper function to build OR condition for multiple command checks + // Uses strict matching: command at start of line only buildMultiCommandCheck := func(bodyAccessor string) ConditionNode { var commandOrChecks []ConditionNode for _, commandName := range commandNames { commandText := fmt.Sprintf("/%s", commandName) - commandOrChecks = append(commandOrChecks, BuildContains( + commandWithSpace := fmt.Sprintf("/%s ", commandName) + + // Check for exact match (command without arguments) + exactMatch := BuildEquals( BuildPropertyAccess(bodyAccessor), BuildStringLiteral(commandText), - )) + ) + + // Check for command with arguments (starts with "/ ") + startsWithMatch := BuildFunctionCall("startsWith", + BuildPropertyAccess(bodyAccessor), + BuildStringLiteral(commandWithSpace), + ) + + // Combine: exact match OR starts with pattern + commandCheck := &OrNode{ + Left: startsWithMatch, + Right: exactMatch, + } + + commandOrChecks = append(commandOrChecks, commandCheck) } // If only one command, return it directly; otherwise combine with OR if len(commandOrChecks) == 1 { diff --git a/pkg/workflow/command_precision_test.go b/pkg/workflow/command_precision_test.go index 4e7fd593df..847206f16c 100644 --- a/pkg/workflow/command_precision_test.go +++ b/pkg/workflow/command_precision_test.go @@ -39,7 +39,8 @@ tools: ---`, filename: "command-issues-precision.md", shouldContain: []string{ - "(github.event_name == 'issues') && (contains(github.event.issue.body", + "(github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/test-bot ')", + "github.event.issue.body == '/test-bot'", }, shouldNotContain: []string{ "github.event.comment.body", @@ -60,11 +61,12 @@ tools: filename: "command-issue-comment-precision.md", shouldContain: []string{ "(github.event_name == 'issue_comment')", - "contains(github.event.comment.body", + "startsWith(github.event.comment.body, '/test-bot ')", + "github.event.comment.body == '/test-bot'", "github.event.issue.pull_request == null", }, shouldNotContain: []string{ - "contains(github.event.issue.body", + "github.event.issue.body", "github.event.pull_request.body", }, }, @@ -81,7 +83,8 @@ tools: ---`, filename: "command-pr-precision.md", shouldContain: []string{ - "(github.event_name == 'pull_request') && (contains(github.event.pull_request.body", + "(github.event_name == 'pull_request') && ((startsWith(github.event.pull_request.body, '/test-bot ')", + "github.event.pull_request.body == '/test-bot'", }, shouldNotContain: []string{ "github.event.issue.body", @@ -102,11 +105,12 @@ tools: filename: "command-pr-comment-precision.md", shouldContain: []string{ "(github.event_name == 'issue_comment')", - "contains(github.event.comment.body", + "startsWith(github.event.comment.body, '/test-bot ')", + "github.event.comment.body == '/test-bot'", "github.event.issue.pull_request != null", }, shouldNotContain: []string{ - "contains(github.event.issue.body", + "github.event.issue.body", "github.event.pull_request.body", }, }, @@ -123,10 +127,11 @@ tools: ---`, filename: "command-pr-review-comment-precision.md", shouldContain: []string{ - "(github.event_name == 'pull_request_review_comment') && (contains(github.event.comment.body", + "(github.event_name == 'pull_request_review_comment') && ((startsWith(github.event.comment.body, '/test-bot ')", + "github.event.comment.body == '/test-bot'", }, shouldNotContain: []string{ - "contains(github.event.issue.body", + "github.event.issue.body", "github.event.pull_request.body", }, }, @@ -143,10 +148,13 @@ tools: ---`, filename: "command-multiple-precision.md", shouldContain: []string{ - "(github.event_name == 'issues') && (contains(github.event.issue.body", + "(github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/test-bot ')", + "github.event.issue.body == '/test-bot'", "(github.event_name == 'issue_comment')", - "contains(github.event.comment.body", - "(github.event_name == 'pull_request') && (contains(github.event.pull_request.body", + "startsWith(github.event.comment.body, '/test-bot ')", + "github.event.comment.body == '/test-bot'", + "(github.event_name == 'pull_request') && ((startsWith(github.event.pull_request.body, '/test-bot ')", + "github.event.pull_request.body == '/test-bot'", }, }, { @@ -164,9 +172,11 @@ tools: ---`, filename: "command-with-push-precision.md", shouldContain: []string{ - "(github.event_name == 'issues') && (contains(github.event.issue.body", + "(github.event_name == 'issues') && ((startsWith(github.event.issue.body, '/test-bot ')", + "github.event.issue.body == '/test-bot'", "(github.event_name == 'issue_comment')", - "contains(github.event.comment.body", + "startsWith(github.event.comment.body, '/test-bot ')", + "github.event.comment.body == '/test-bot'", }, shouldNotContain: []string{ // Should not check issue.body when event is issue_comment diff --git a/pkg/workflow/command_test.go b/pkg/workflow/command_test.go index 84e1b13b1e..5951224b03 100644 --- a/pkg/workflow/command_test.go +++ b/pkg/workflow/command_test.go @@ -214,29 +214,35 @@ This test validates that command conditions are applied correctly based on event if tt.expectedSimpleCondition { // Should contain simple command condition (no complex event_name logic in main job) - expectedPattern := "contains(github.event.issue.body, '/" - if !strings.Contains(lockContentStr, expectedPattern) { - t.Errorf("Expected simple command condition containing '%s' but not found", expectedPattern) + // Check for strict matching patterns: startsWith or exact equality + startsWithPattern := "startsWith(github.event.issue.body, '/" + exactMatchPattern := "github.event.issue.body == '/" + + hasStartsWith := strings.Contains(lockContentStr, startsWithPattern) + hasExactMatch := strings.Contains(lockContentStr, exactMatchPattern) + + if !hasStartsWith && !hasExactMatch { + t.Errorf("Expected simple command condition with either startsWith or exact match, but found neither") } // For simple command workflows, the main job condition should not contain github.event_name logic - // We can check this by looking for conditions that use "contains(" without "github.event_name" + // We can check this by looking for conditions that use "startsWith(" or equality checks without "github.event_name" // Handle both single-line and multi-line YAML conditions lines := strings.Split(lockContentStr, "\n") foundSimpleCommandCondition := false for i, line := range lines { // Check for single-line if condition - if strings.Contains(line, "if:") && strings.Contains(line, "contains(") && !strings.Contains(line, "github.event_name") { + if strings.Contains(line, "if:") && (strings.Contains(line, "startsWith(") || strings.Contains(line, ".body == '/")) && !strings.Contains(line, "github.event_name") { foundSimpleCommandCondition = true break } // Check for multi-line if condition (if: > or if: | format) if strings.Contains(line, "if:") && (strings.Contains(line, ">") || strings.Contains(line, "|")) { - // Check the following lines for contains() without github.event_name + // Check the following lines for startsWith() or exact match without github.event_name for j := i + 1; j < len(lines) && strings.TrimSpace(lines[j]) != ""; j++ { nextLine := lines[j] - if strings.Contains(nextLine, "contains(") && !strings.Contains(nextLine, "github.event_name") { + if (strings.Contains(nextLine, "startsWith(") || strings.Contains(nextLine, ".body == '/")) && !strings.Contains(nextLine, "github.event_name") { foundSimpleCommandCondition = true break } @@ -251,7 +257,7 @@ This test validates that command conditions are applied correctly based on event } } if !foundSimpleCommandCondition { - t.Errorf("Expected to find simple command condition (contains without github.event_name) but not found") + t.Errorf("Expected to find simple command condition (startsWith or exact match without github.event_name) but not found") } } diff --git a/pkg/workflow/compiler_events_test.go b/pkg/workflow/compiler_events_test.go index 0273201f78..ba95ca79bf 100644 --- a/pkg/workflow/compiler_events_test.go +++ b/pkg/workflow/compiler_events_test.go @@ -153,7 +153,7 @@ tools: ---`, filename: "test-bot.md", expectedOn: "pull_request_review_comment:\n types:\n - created\n - edited", - expectedIf: "contains(github.event.issue.body, '/test-bot')", + expectedIf: "startsWith(github.event.issue.body, '/test-bot ')", expectedCommand: "test-bot", }, { @@ -168,7 +168,7 @@ tools: ---`, filename: "test-new-format.md", expectedOn: "pull_request_review_comment:\n types:\n - created\n - edited", - expectedIf: "contains(github.event.issue.body, '/new-bot')", + expectedIf: "startsWith(github.event.issue.body, '/new-bot ')", expectedCommand: "new-bot", }, { @@ -182,7 +182,7 @@ tools: ---`, filename: "default-name-bot.md", expectedOn: "pull_request_review_comment:\n types:\n - created\n - edited", - expectedIf: "contains(github.event.issue.body, '/default-name-bot')", + expectedIf: "startsWith(github.event.issue.body, '/default-name-bot ')", expectedCommand: "default-name-bot", }, { @@ -196,7 +196,7 @@ tools: ---`, filename: "test-string-format.md", expectedOn: "pull_request_review_comment:\n types:\n - created\n - edited", - expectedIf: "contains(github.event.issue.body, '/customname')", + expectedIf: "startsWith(github.event.issue.body, '/customname ')", expectedCommand: "customname", }, } From 063adc463d8001cb0da079275c0cb980ce17ac28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:43:50 +0000 Subject: [PATCH 3/4] Fix permission_restriction_test to expect strict matching patterns Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/command.go | 8 ++++---- pkg/workflow/command_test.go | 4 ++-- pkg/workflow/permission_restriction_test.go | 9 ++++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pkg/workflow/command.go b/pkg/workflow/command.go index ee7f2db893..d659c0e6b3 100644 --- a/pkg/workflow/command.go +++ b/pkg/workflow/command.go @@ -44,25 +44,25 @@ func buildEventAwareCommandCondition(commandNames []string, commandEvents []stri for _, commandName := range commandNames { commandText := fmt.Sprintf("/%s", commandName) commandWithSpace := fmt.Sprintf("/%s ", commandName) - + // Check for exact match (command without arguments) exactMatch := BuildEquals( BuildPropertyAccess(bodyAccessor), BuildStringLiteral(commandText), ) - + // Check for command with arguments (starts with "/ ") startsWithMatch := BuildFunctionCall("startsWith", BuildPropertyAccess(bodyAccessor), BuildStringLiteral(commandWithSpace), ) - + // Combine: exact match OR starts with pattern commandCheck := &OrNode{ Left: startsWithMatch, Right: exactMatch, } - + commandOrChecks = append(commandOrChecks, commandCheck) } // If only one command, return it directly; otherwise combine with OR diff --git a/pkg/workflow/command_test.go b/pkg/workflow/command_test.go index 5951224b03..4ed4f00e6a 100644 --- a/pkg/workflow/command_test.go +++ b/pkg/workflow/command_test.go @@ -217,10 +217,10 @@ This test validates that command conditions are applied correctly based on event // Check for strict matching patterns: startsWith or exact equality startsWithPattern := "startsWith(github.event.issue.body, '/" exactMatchPattern := "github.event.issue.body == '/" - + hasStartsWith := strings.Contains(lockContentStr, startsWithPattern) hasExactMatch := strings.Contains(lockContentStr, exactMatchPattern) - + if !hasStartsWith && !hasExactMatch { t.Errorf("Expected simple command condition with either startsWith or exact match, but found neither") } diff --git a/pkg/workflow/permission_restriction_test.go b/pkg/workflow/permission_restriction_test.go index 1d6afb05c8..c8550a025b 100644 --- a/pkg/workflow/permission_restriction_test.go +++ b/pkg/workflow/permission_restriction_test.go @@ -299,8 +299,11 @@ Test workflow content.` t.Errorf("Expected permission check to be present in command workflow") } - // Check that it includes command-specific condition logic - if !strings.Contains(lockContentStr, "contains(github.event") { - t.Errorf("Expected command-specific conditional logic in permission check") + // Check that it includes command-specific condition logic (strict matching) + hasStartsWith := strings.Contains(lockContentStr, "startsWith(github.event") + hasExactMatch := strings.Contains(lockContentStr, ".body == '/") + + if !hasStartsWith && !hasExactMatch { + t.Errorf("Expected command-specific conditional logic (startsWith or exact match) in permission check") } } From a7d1f51560a68f11609906b9cab934518cea1bb2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 00:48:33 +0000 Subject: [PATCH 4/4] Add changeset [skip-ci] --- .changeset/patch-strict-slash-command-matching.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-strict-slash-command-matching.md diff --git a/.changeset/patch-strict-slash-command-matching.md b/.changeset/patch-strict-slash-command-matching.md new file mode 100644 index 0000000000..2235c00c2f --- /dev/null +++ b/.changeset/patch-strict-slash-command-matching.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Prevent slash commands from matching when they appear mid-sentence by using strict regex checks that only match at the start of the event body.