Fix slash_command activation for bot comments with newline-separated metadata#22013
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/53b83b6a-ea5b-4826-878b-91924f01265d
- In command.go: add startsWith(body, '/command\n') check to handle bot comments that append attribution metadata after a newline - In expression_parser.go: add hasNewlineInStringLiteral() and escapeForYAMLDoubleQuoted() helpers - In jobs.go: use YAML double-quoted scalar when the if: condition contains a literal newline inside a string literal, so the \n escape is preserved correctly after YAML parsing - Update command_precision_test.go: add newline check patterns, new dedicated bot-comment test case - Update command_test.go: handle double-quoted YAML if: format in test - Add bot comment tests to check_command_position.test.cjs - Recompile all 176 lock files Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/53b83b6a-ea5b-4826-878b-91924f01265d
…uoted Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/53b83b6a-ea5b-4826-878b-91924f01265d
There was a problem hiding this comment.
Pull request overview
Fixes slash-command activation for bot comments that include newline-separated attribution/metadata by ensuring both the generated expression and the emitted YAML preserve newline semantics.
Changes:
- Extend generated slash-command matching to include
/command\n(newline-terminated first line) in addition to/commandand/command. - Add detection/escaping utilities so
if:expressions containing newline characters inside string literals are emitted as YAML double-quoted scalars (preserving\nthrough YAML parsing). - Update/add Go + JS tests and recompile workflow lock files to reflect the new encoding and matching behavior.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/command.go | Adds startsWith(body, '/<cmd>\n') branch to slash-command activation condition generation. |
| pkg/workflow/expression_parser.go | Adds helpers to detect newline bytes inside single-quoted expression literals and escape expressions for YAML double-quoted scalars. |
| pkg/workflow/jobs.go | Emits if: "..." (double-quoted YAML) when the expression contains a newline inside a string literal to avoid folded-scalar newline folding. |
| pkg/workflow/command_test.go | Updates test logic to accept double-quoted YAML if: emission format. |
| pkg/workflow/command_precision_test.go | Updates precision assertions and adds a dedicated bot-comment-with-metadata test case. |
| pkg/workflow/yaml_encoding_test.go | Adds unit coverage for newline detection and YAML double-quote escaping helpers. |
| actions/setup/js/check_command_position.test.cjs | Adds tests ensuring bot comments with newline-separated metadata are accepted as “command at start”. |
| .github/workflows/unbloat-docs.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/tidy.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/security-review.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/scout.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/q.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/pr-nitpick-reviewer.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/poem-bot.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/plan.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/pdf-summary.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/mergefest.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/grumpy-reviewer.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/craft.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/cloclo.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/brave.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/archie.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
| .github/workflows/ace-editor.lock.yml | Recompiled lock workflow reflecting new /cmd\n matching and YAML encoding. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // the expected command patterns. | ||
| if strings.Contains(line, "if: \"") && (strings.Contains(line, "startsWith(") || strings.Contains(line, ".body == '/")) { |
There was a problem hiding this comment.
In the new branch that accepts YAML double-quoted single-line if: conditions, the test no longer enforces the original requirement that “simple command” workflows must not include github.event_name logic. As written, this can allow event-aware conditions to satisfy the simple-condition check as long as they’re emitted as if: "...". Consider keeping the same !strings.Contains(line, "github.event_name") guard here (or otherwise asserting the absence of event gating) so the test continues to validate the intended behavior.
| // the expected command patterns. | |
| if strings.Contains(line, "if: \"") && (strings.Contains(line, "startsWith(") || strings.Contains(line, ".body == '/")) { | |
| // the expected command patterns and does not gate on github.event_name. | |
| if strings.Contains(line, "if: \"") && (strings.Contains(line, "startsWith(") || strings.Contains(line, ".body == '/")) && !strings.Contains(line, "github.event_name") { |
|
@copilot review comments |
Replace the fragile line-by-line search (which looked for startsWith without event_name in the rendered YAML) with a more robust check that verifies the absence of the non-comment event passthrough `!(github.event_name ==`. Simple command-only workflows should not include that passthrough; event-aware ones always do. This is more meaningful than checking whether event_name appears in a startsWith line (it always does with the double-quoted YAML format), and it directly tests the intended behavioral property. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3223ee67-70a3-488b-8e7e-086e172d5f7e
Addressed in 9df3fd7. The double-quoted |
Bot comments like
/command\n> Generated by [Workflow Name](...)silently skip activation because the compiledif:condition only matchesstartsWith(body, '/command ')orbody == '/command'— neither covers a newline-terminated first line.Changes
pkg/workflow/command.goAdded
startsWith(body, '/command\n')as a third branch inbuildMultiCommandCheckalongside the existing space and exact-match checks.pkg/workflow/expression_parser.goTwo new helpers:
hasNewlineInStringLiteral(s)— detects a literal\nbyte inside a'...'GitHub Actions expression stringescapeForYAMLDoubleQuoted(s)— escapes\n,\\,",\r,\tfor YAML double-quoted scalarspkg/workflow/jobs.goWhen
hasNewlineInStringLiteralis true, theif:block is emitted as a YAML double-quoted scalar (if: "...") instead of a folded block scalar (if: >). YAML's\nescape in double-quoted strings round-trips through the YAML parser as a real newline, so GitHub Actions evaluatesstartsWith(body, '/command<LF>')correctly.Before (folded scalar folds
\n→ space, corrupting the check):After (double-quoted scalar preserves
\nescape):Tests
command_precision_test.go— patterns updated for new structure; dedicated bot-comment test case addedcheck_command_position.test.cjs— two new tests for newline-terminated bot comment bodiesyaml_encoding_test.go— new: unit tests forhasNewlineInStringLiteral(10 cases) andescapeForYAMLDoubleQuoted(9 cases)Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
https://api.github.com/graphql/usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw 64/pkg/tool/linurev-parse n-dir/bash git rev-�� --show-toplevel ache/go/1.25.0/x64/pkg/tool/linu-trimpath /usr/bin/git 87/001/test-emptgit g_.a 64/pkg/tool/linu--show-toplevel git(http block)/usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw -buildtags 7611433/b428/vetrun git rev-�� --show-toplevel git /usr/bin/git /tmp/TestGuardPobash rev-parse /usr/bin/infocmp--noprofile git(http block)/usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw -importcfg /usr/bin/git git rev-�� --show-toplevel git /usr/bin/git --show-toplevel l /tmp/go-build246--noprofile git(http block)https://api.github.com/orgs/test-owner/actions/secrets/usr/bin/gh gh api /orgs/test-owner/actions/secrets --jq .secrets[].name se 0546683/b086/vet.prettierignore x86_64/bash _workflow.go branch_validatio-c n-dir/git ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet(http block)https://api.github.com/repos/actions-ecosystem/action-add-labels/contents/action.yaml/usr/bin/gh gh api /repos/actions-ecosystem/action-add-labels/contents/action.yaml?ref=18f1af5e3544586314bbe15c0273249c770b2daf --jq .content --show-toplevel git /usr/bin/git --show-toplevel /opt/hostedtoolc-C /usr/bin/git git rev-�� --count e77b6100abc0fa8e302b976de6fef112a283f95c..HEAD r: $owner, name: $name) { hasDiscussionsEnabled } } n-dir/bash /opt/hostedtoolc/opt/hostedtoolcache/node/24.14.0/x64/bin/npm /opt/hostedtoolcview git(http block)https://api.github.com/repos/actions-ecosystem/action-add-labels/contents/action.yml/usr/bin/gh gh api /repos/actions-ecosystem/action-add-labels/contents/action.yml?ref=18f1af5e3544586314bbe15c0273249c770b2daf --jq .content --show-toplevel git r: $owner, name: $name) { hasDiscussionsEnabled } } .github/workflow/usr/bin/gh /opt/hostedtoolcapi /usr/bin/git git rev-�� HEAD git /usr/bin/git --show-toplevel /opt/hostedtoolcview /opt/hostedtoolc@sentry/mcp-server@0.29.0 git(http block)https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1/usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha --show-toplevel /opt/hostedtoolcconfig /usr/bin/git on' --ignore-patgit /tmp/go-build118rev-parse _modules/.bin/no--show-toplevel git rev-�� --show-toplevel ache/go/1.25.0/x64/pkg/tool/linuHEAD /usr/bin/git -bool -buildtags kflow.test git(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v3/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha --noprofile 64/pkg/tool/linu-trimpath /tmp/go-build2467611433/b001/gh-aw.test ignore-path ../.git .cfg 0/x64/bin/bash /tmp/go-build2467611433/b001/gh-aw.test -tes�� -test.paniconexit0 -test.v=true /usr/bin/git -test.timeout=10git -test.run=^Test -test.short=true--show-toplevel git(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v5/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --noprofile git x86_64/git son --local ache/node/24.14.vars.MY_VAR git -c log.showsignature=false log e249fcad -n1 --format=format:rev-parse 85683b1017dc /opt/hostedtoolcache/go/1.25.0/x.github/workflows/test.md(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --git-dir bash /usr/bin/git --noprofile -tests x_amd64/vet git rev-�� --show-toplevel x_amd64/vet /usr/bin/git */*.ts' '**/*.jsgit git tions/node_modul--show-toplevel git(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel erignore /usr/bin/git 1235128698 HEAD /home/REDACTED/go/--show-toplevel git rev-�� --show-toplevel ortcfg /usr/bin/git om/aymanbagabas/git om/aymanbagabas/rev-parse ache/go/1.25.0/x--show-toplevel git(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v6/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha user.name Test User /usr/bin/git -unreachable=falgit /tmp/go-build118rev-parse 64/bin/bash git conf�� user.email test@example.com /usr/bin/git -unreachable=falgit /tmp/go-build118rev-parse ode_modules/.bin--show-toplevel git(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha /tmp/TestHashConsistency_GoAndJavaScript1589879987/001/test-complex-frontmatter-with-tools.md /opt/hostedtoolcHEAD /usr/bin/git -unreachable=falgit /tmp/go-build118rev-parse k/_temp/ghcca-no--show-toplevel git conf�� user.name Test User /usr/bin/git on' --ignore-patgit /tmp/go-build118rev-parse odules/npm/node_--show-toplevel git(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel 64/pkg/tool/linux_amd64/vet /usr/bin/git 414/001/stabilitgit HEAD h git rev-�� --show-toplevel 2730341/b374/importcfg /usr/bin/git Onlyrepos_only_wgit k/gh-aw/gh-aw/pkrev-parse .cfg git(http block)https://api.github.com/repos/actions/github-script/git/ref/tags/v8/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha ath ../../../.pr**/*.json 0546683/b138/vet--ignore-path 64/bin/bash OUTPUT -d 168.63.129.16 ache/go/1.25.0/x64/pkg/tool/linuHEAD --no�� re --log-level=error conntrack de/node/bin/bash INVALID,NEW -j DROP ache/go/1.25.0/xHEAD(http block)/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha re --log-level=e!../../../pkg/workflow/js/**/*.json 0546683/b232/vet--ignore-path ache/go/1.25.0/x../../../.prettierignore(http block)/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha re --log-level=error 0546683/b241/vet.cfg ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet(http block)https://api.github.com/repos/actions/setup-go/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha --get remote.origin.url /usr/bin/git -unreachable=falgit /tmp/go-build118rev-parse _modules/.bin/sh--show-toplevel git bran�� --show-current /opt/hostedtoolc-test.v=true /usr/bin/git on' --ignore-patgit -buildtags ash git(http block)https://api.github.com/repos/actions/setup-node/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha add origin /usr/bin/git -unreachable=falgit /tmp/go-build118rev-parse ode_modules/.bin--show-toplevel git comm�� -m Initial commit /opt/hostedtoolcache/node/24.14.0/x64/bin/node on' --ignore-patgit /tmp/go-build118rev-parse es/.bin/sh node(http block)https://api.github.com/repos/actions/setup-node/git/ref/tags/v6/usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v6 --jq .object.sha --show-toplevel node $name) { hasDiscussionsEnabled } } /home/REDACTED/worinfocmp /opt/hostedtoolc-1 /opt/hostedtoolcxterm-color git rev-�� --show-toplevel /opt/hostedtoolcache/node/24.14.0/x64/bin/node 64/bin/git secrets.TOKEN bash /usr/bin/git git(http block)/usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v6 --jq .object.sha --show-toplevel git $name) { hasDiscussionsEnabled } } --show-toplevel /opt/hostedtoolc-C /usr/bin/git git rev-�� --show-toplevel git de/node/bin/git --show-toplevel bash /usr/bin/git git(http block)https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha 7611433/b430/repoutil.test 64/pkg/tool/linuHEAD 7611433/b430/importcfg.link --noprofile(http block)https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b/usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha --show-toplevel git modules/@npmcli/run-script/lib/node-gyp-bin/node user.name Test User /usr/bin/git git /tsc�� --show-toplevel git /home/REDACTED/go/bin/node --show-toplevel(http block)https://api.github.com/repos/github/gh-aw/usr/bin/gh gh api /repos/github/gh-aw --jq .visibility --show-toplevel git sh --git-dir 64/pkg/tool/linuapi /usr/bin/git bash /tmp�� 143443/b001/work-f git x_amd64/cgo --git-dir 143443/b001/workimage /usr/bin/git x_amd64/cgo(http block)https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0/usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha ithub/workflows/ai-moderator.md 64/pkg/tool/linu-run /home/REDACTED/node_modules/.bin/node tierignore(http block)https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3/usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha "prettier" --write 'scripts/**/*.js' --ignore-path .prettierignore --log-level=error 64/pkg/tool/linuHEAD .cfg tierignore .cfg 64/pkg/tool/linu--show-toplevel node /hom�� k/gh-aw/gh-aw/.github/workflows ../../../**/*.json ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet --ignore-path ../../../.prettirev-parse ache/go/1.25.0/x--show-toplevel ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet(http block)https://api.github.com/repos/github/gh-aw/actions/runs/1/artifacts/usr/bin/gh gh run download 1 --dir test-logs/run-1 -tests x_amd64/vet(http block)https://api.github.com/repos/github/gh-aw/actions/runs/12345/artifacts/usr/bin/gh gh run download 12345 --dir test-logs/run-12345 o x_amd64/compile(http block)https://api.github.com/repos/github/gh-aw/actions/runs/12346/artifacts/usr/bin/gh gh run download 12346 --dir test-logs/run-12346 ns_validation_test.go x_amd64/compile submodules | heagit(http block)https://api.github.com/repos/github/gh-aw/actions/runs/2/artifacts/usr/bin/gh gh run download 2 --dir test-logs/run-2 -tests x_amd64/compile /home/REDACTED/worgit(http block)https://api.github.com/repos/github/gh-aw/actions/runs/3/artifacts/usr/bin/gh gh run download 3 --dir test-logs/run-3 -tests /snap/bin/bash /home/REDACTED/worgit(http block)https://api.github.com/repos/github/gh-aw/actions/runs/4/artifacts/usr/bin/gh gh run download 4 --dir test-logs/run-4 -run x_amd64/compile ./pkg/workflow/ --global k/_temp/ghcca-nouser.email x_amd64/compile --no�� .js' --ignore-path .prettierignore --log-level=e!../../../pkg/workflow/js/**/*.json git tions/setup/js/node_modules/.bin/prettier get --local /opt/hostedtoolc--show-toplevel sed(http block)https://api.github.com/repos/github/gh-aw/actions/runs/5/artifacts/usr/bin/gh gh run download 5 --dir test-logs/run-5 -tests n-dir/sh get --global /home/REDACTED/go//home/REDACTED/work/gh-aw/gh-aw/.github/workflows/artifacts-summary.md bash --no�� .js' --ignore-path .prettierignore --log-level=e!../../../pkg/workflow/js/**/*.json git tions/setup/js/node_modules/.bin/node get --local /opt/hostedtoolc--show-toplevel ache/go/1.25.0/x64/pkg/tool/linux_amd64/compile(http block)https://api.github.com/repos/github/gh-aw/actions/workflows/usr/bin/gh gh workflow list --json name,state,path --noprofile(http block)/usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 100(http block)/usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 6 -d 168.63.129.16 git tion�� extensions.objectformat 53 k/_temp/ghcca-node/node/bin/git ACCEPT --local k/_temp/ghcca-no--show-toplevel bash(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha */*.ts' '**/*.json' --ignore-path ../../../.pret.prettierignore git 49cf3a2b9db41c5ce249fcad ir.go --local(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.2.3/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.2.3 --jq .object.sha js/**/*.json' --ignore-path ../../../.prettierignore(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v2.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq .object.sha js/**/*.json' --ignore-path ../../../.prettierignore(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v3.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v3.0.0 --jq .object.sha js/**/*.json' --ignore-path ../../../.prettierignore(http block)https://api.github.com/repos/githubnext/agentics/git/ref/tags//usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha --show-toplevel git /usr/bin/git(http block)https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999/usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha */*.ts' '**/*.js-c=4 git ndor/bin/sh --get-regexp --local 64/pkg/tool/linu--git-dir bash --no�� */*.json' '!../../../pkg/workflow/js/**/*.json' --ignore-path 64/pkg/tool/linu-buildtags x_amd64/compile get --local de/node/bin/bash--show-toplevel x_amd64/compile(http block)https://api.github.com/repos/nonexistent/repo/actions/runs/12345Co-arev-parse` (http block)
📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.