Skip to content

fix(cli): address 7 CLI consistency issues across help text and flag behavior#25658

Merged
pelikhan merged 2 commits intomainfrom
copilot/cli-consistency-issues-resolution
Apr 10, 2026
Merged

fix(cli): address 7 CLI consistency issues across help text and flag behavior#25658
pelikhan merged 2 commits intomainfrom
copilot/cli-consistency-issues-resolution

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 10, 2026

Automated CLI inspection identified 7 inconsistencies across 7 commands — 2 medium-severity behavioral mismatches and 5 low-severity help text issues.

Behavioral fixes

  • --dir flag semantic inconsistency (add, add-wizard): These two commands treated --dir as a subdirectory shorthand under .github/workflows/ (e.g. --dir shared.github/workflows/shared/), while compile, fix, and upgrade treat it as a full path. Aligned add/add-wizard to use full-path semantics — users must now pass --dir .github/workflows/shared explicitly. Updated integration test accordingly.

  • --no-fix misleading description (upgrade): Flag description implied it only skipped codemods; it actually skips codemods + action version updates + compilation. New description: "Skip codemods, action version updates, and workflow compilation (only update agent files)".

  • remove missing --dir flag: Added -d/--dir to remove, consistent with all other workflow-file commands. Updated RemoveWorkflows() signature to accept workflowDir string.

Help text fixes

  • trial non-standard verb: Long description opened with "Trial one or more…" → "Run one or more agentic workflows in trial mode…"
  • run implementation detail: Removed "It executes gh workflow run <workflow-lock-file>…" from help text.
  • mcp add error case in docs: Removed sentence documenting what happens when only one argument is passed.
  • pr transfer placeholder casing: Standardized both examples to lowercase owner/repo (was mixing trial/repo and PR-OWNER/PR-REPO).

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
    • Triggering command: /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 bash /opt/hostedtoolc--show-toplevel git rev-�� --show-toplevel /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/compile /usr/bin/gh (http block)
    • Triggering command: /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 GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE node (http block)
    • Triggering command: /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 GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE sh (http block)
  • https://api.github.com/orgs/test-owner/actions/secrets
    • Triggering command: /usr/bin/gh gh api /orgs/test-owner/actions/secrets --jq .secrets[].name ath ../../../.pr**/*.json (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha --show-toplevel /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linu-trimpath /usr/bin/git -unreachable=falgit /tmp/go-build239rev-parse son git rev-�� --show-toplevel /opt/hostedtoolcremote.origin.url /usr/bin/git 92/001/test-fron/opt/hostedtoolcache/node/24.14.1/x64/bin/npm sRemoteWithRealGinstall k/_temp/ghcca-no--package-lock-only git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha /tmp/gh-aw-test-runs/20260410-145040-13749/test-1428162500/.github/workflows config om/upstream/repo.git remote.origin.urgit .cfg 64/pkg/tool/linu--show-toplevel git remo�� (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha te &#39;**/*.cjs&#39; &#39;**/*.ts&#39; &#39;**/*.json&#39; --ignore-path ../../../.prettierignore -buildtags in/sh -errorsas -ifaceassert -d /opt/hostedtoolcache/go/1.25.8/xorigin -ato�� Onlymin-integrity_with_repos=public_2662114098/001 -buildtags ache/go/1.25.8/x64/pkg/tool/linux_amd64/asm -errorsas -ifaceassert -nilfunc ache/go/1.25.8/x64/pkg/tool/linux_amd64/asm (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel /opt/hostedtoolc^remote\..*\.gh-resolved$ /usr/bin/git y.md -buildtags ache/node/24.14.--show-toplevel git rev-�� --show-toplevel head /usr/bin/git i.go ntifiers.go 1/x64/bin/node git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel ps /usr/bin/git t 64/pkg/tool/linurev-parse om/owner/repo.gi--show-toplevel git rev-�� --show-toplevel git /usr/bin/git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet /usr/bin/git */*.ts&#39; &#39;**/*.jsgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha rgo/bin/git YM/0vZ-SQXr-bLUsrev-parse /usr/bin/git */*.ts&#39; &#39;**/*.jsgit 9601270/b292/vetrev-parse 9de957c76f39f056--show-toplevel git rev-�� --show-toplevel /opt/hostedtoolcache/go/1.25.8/xremote /usr/bin/git ut1267902670/001git -buildtags 64/pkg/tool/linu--show-toplevel git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel git /usr/bin/git bility_SameInputgit x_amd64/vet /usr/bin/git git rev-�� --show-toplevel git /usr/bin/git /tmp/gh-aw-test-git config mple.com/org/rep--show-toplevel git (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v9
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v9 --jq .object.sha test (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v9 --jq .object.sha --noprofile .cfg 64/pkg/tool/linux_amd64/vet (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v9 --jq .object.sha istency-issues-resolution .cfg 64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha --git-dir ache/go/1.25.8/x-goversion /usr/bin/docker */*.ts&#39; &#39;**/*.jsgit (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha --show-toplevel ache/go/1.25.8/x-buildtags /usr/bin/git */*.ts&#39; &#39;**/*.jsgit (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha --show-toplevel 5yTZfZI/KgaAFRnBxEEijlOfVdEX 1/x64/bin/node --noprofile (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha -json GO111MODULE $name) { hasDiscussionsEnabled } } GOINSECURE GOMOD GOMODCACHE node /hom�� --check **/*.cjs 64/bin/go **/*.json --ignore-path ../../../.prettiHEAD:.github/workflows/brave.lock.yml go (http block)
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE node /hom�� --check **/*.cjs 64/bin/go **/*.json --ignore-path ../../../.pretti-c go (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .visibility -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0.1.2
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0.1.2 --jq .object.sha --show-toplevel ache/go/1.25.8/x-extld=gcc /usr/bin/git mLsRemoteWithReagit mLsRemoteWithRearev-parse -tmpfiles /usr/bin/git conf�� --get-regexp ^remote\..*\.gh-resolved$ /usr/bin/git 1856197363/.githgit /tmp/go-build239rev-parse de/node/bin/bash--show-toplevel git (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq .object.sha /tmp/go-build399998881/b426/_pkg_.a -trimpath /usr/bin/git -p main -lang=go1.25 git -C /tmp/TestGuardPolicyBlockedUsersExpressionCompiledOutput3967088594/001 (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq .object.sha git-upload-pack &#39;/tmp/TestParseDefaultBranchFromLsRemoteWithReal-p l /usr/bin/git --noprofile (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/1/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/1/artifacts --jq .artifacts[].name -trimpath cal/bin/bash -p github.com/stret/tmp/js-hash-test-1070469790/test-hash.js -lang=go1.17 /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linuTest User tion�� 513018392 /tmp/go-build2399601270/b125/vet.cfg son ignore -nolocalimports -importcfg /opt/hostedtoolcache/go/1.25.8/x-trimpath (http block)
    • Triggering command: /usr/bin/gh gh run download 1 --dir test-logs/run-1 /tmp/go-build2399601270/b021/vet.cfg ndor/bin/bash (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/12345/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/12345/artifacts --jq .artifacts[].name (http block)
    • Triggering command: /usr/bin/gh gh run download 12345 --dir test-logs/run-12345 -trimpath tions/setup/js/node_modules/.bin/node -p internal/fuzz -lang=go1.25 ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet tion�� 864621718 /tmp/go-build2399601270/b095/vet.cfg son ignore -c=4 -nolocalimports /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linu-importcfg (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/12346/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/12346/artifacts --jq .artifacts[].name (http block)
    • Triggering command: /usr/bin/gh gh run download 12346 --dir test-logs/run-12346 (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/2/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/2/artifacts --jq .artifacts[].name -trimpath bin/bash -p testing/internalinit -lang=go1.25 /opt/hostedtoolc/tmp/go-build399998881/b444/_testmain.go tion�� -unreachable=false /tmp/go-build2399601270/b137/vet.cfg son ignore -c=4 -nolocalimports /opt/hostedtoolcache/go/1.25.8/x-buildtags (http block)
    • Triggering command: /usr/bin/gh gh run download 2 --dir test-logs/run-2 /tmp/go-build2399601270/b026/vet.cfg ache/uv/0.11.6/x86_64/bash (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/3/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/3/artifacts --jq .artifacts[].name -trimpath n-dir/node -p github.com/githuconfig -lang=go1.25 /opt/hostedtoolctest@example.com tion�� 513018392 /tmp/go-build2399601270/b141/vet-ifaceassert son ignore -nolocalimports -importcfg /opt/hostedtoolcache/go/1.25.8/x-tests (http block)
    • Triggering command: /usr/bin/gh gh run download 3 --dir test-logs/run-3 /tmp/go-build2399601270/b028/vetmain bin/bash (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/4/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/4/artifacts --jq .artifacts[].name -trimpath 1/x64/lib/node_modules/npm/node_modules/@npmcli/run-script/lib/node-gyp-bin/node -p github.com/stretconfig -lang=go1.17 /opt/hostedtoolcTest User tion�� 513018392 /tmp/go-build2399601270/b116/vet.cfg son ignore -nolocalimports -importcfg /opt/hostedtoolcache/go/1.25.8/x-importcfg (http block)
    • Triggering command: /usr/bin/gh gh run download 4 --dir test-logs/run-4 /tmp/go-build2399601270/b029/vet.cfg ache/go/1.25.8/x64/bin/bash (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/5/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/5/artifacts --jq .artifacts[].name /tmp/go-build2399601270/b007/vet.cfg 86_64/node (http block)
    • Triggering command: /usr/bin/gh gh run download 5 --dir test-logs/run-5 /tmp/go-build2399601270/b025/vet.cfg k/_temp/uv-python-dir/bash (http block)
  • https://api.github.com/repos/github/gh-aw/actions/workflows
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path ath ../../../.pr**/*.json k/gh-aw/gh-aw/pk--ignore-path 64/pkg/tool/linu../../../.prettierignore (http block)
    • Triggering command: /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)
    • Triggering command: /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 (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v0.47.4
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq .object.sha --show-toplevel bash /usr/bin/git 4005942396 on 1/x64/bin/node git rev-�� --show-toplevel sh /usr/bin/git vaScript34935934git git /opt/hostedtoolc--show-toplevel git (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha b/workflows /tmp/go-build2399601270/b067/vet.cfg rgo/bin/bash (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.2.3 --jq .object.sha r\|dir.*flag .cfg 64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v2.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq .object.sha --noprofile (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq .object.sha -c=4 -nolocalimports -importcfg /tmp/go-build399998881/b424/importcfg -pack /home/REDACTED/work/gh-aw/gh-aw/pkg/parser/import_conflict_test.go /home/REDACTED/work/gh-aw/gh-aw/pkg/parser/import_cycle_test.go --no�� --noprofile .cfg ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v3.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v3.0.0 --jq .object.sha --noprofile (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/-
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/- --jq .object.sha /tmp/go-build2041946032/b416/_pkGOINSECURE -trimpath 64/bin/go -p github.com/githu-C -lang=go1.25 go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha *.json&#39; &#39;!../../../pkg/workflow/js/**/*.json&#39; --ignore-path ../../../.prettierignore /tmp/go-build2399601270/b048/vet.cfg x_amd64/vet (http block)
  • https://api.github.com/repos/nonexistent/repo/actions/runs/12345
    • Triggering command: /usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion -errorsas -ifaceassert -nilfunc 64/pkg/tool/linux_amd64/vet -ato�� -bool -buildtags cal/bin/sh nore -ifaceassert -nilfunc /opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linuconfig (http block)
  • https://api.github.com/repos/owner/repo/actions/workflows
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path --repo owner/repo 64/pkg/tool/linu../../../.prettierignore (http block)
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path --repo owner/repo (http block)
  • https://api.github.com/repos/owner/repo/contents/file.md
    • Triggering command: /tmp/go-build399998881/b397/cli.test /tmp/go-build399998881/b397/cli.test -test.testlogfile=/tmp/go-build399998881/b397/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true (http block)
  • https://api.github.com/repos/test-owner/test-repo/actions/secrets
    • Triggering command: /usr/bin/gh gh api /repos/test-owner/test-repo/actions/secrets --jq .secrets[].name ath ../../../.pr**/*.json (http block)

If you need me to access, download, or install something from one of these locations, you can either:

- align --dir flag semantics in add/add-wizard with compile/fix/upgrade
- improve --no-fix description to clearly list what gets skipped
- fix trial command Long description (replace non-standard 'Trial' verb)
- remove implementation detail from run command help text
- remove error case documentation from mcp add help text
- standardize pr transfer examples to use consistent lowercase placeholders
- add --dir flag to remove command with matching semantics"

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/10b3c025-ce46-4975-a469-fb0030f51cb1

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix CLI consistency issues in command help text fix(cli): address 7 CLI consistency issues across help text and flag behavior Apr 10, 2026
Copilot AI requested a review from pelikhan April 10, 2026 15:03
@pelikhan pelikhan marked this pull request as ready for review April 10, 2026 15:13
Copilot AI review requested due to automatic review settings April 10, 2026 15:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Aligns gh aw CLI behavior and help output to remove consistency mismatches identified by automated inspection, focusing on --dir semantics and clearer flag/command documentation.

Changes:

  • Standardizes --dir help text (and add behavior) to treat it as a full workflow directory path rather than a .github/workflows/<subdir> shorthand.
  • Adds --dir to remove and updates the RemoveWorkflows API and call sites.
  • Cleans up/standardizes various help text strings and examples across commands.
Show a summary per file
File Description
pkg/cli/upgrade_command.go Updates --no-fix flag description to match actual behavior.
pkg/cli/trial_command.go Normalizes long description wording (“Run … in trial mode …”).
pkg/cli/remove_command.go Adds workflowDir parameter to RemoveWorkflows and logs it.
pkg/cli/pr_command.go Standardizes pr transfer examples to lowercase owner/repo.
pkg/cli/mcp_add.go Removes a misleading/overly specific help sentence.
pkg/cli/commands_test.go Updates tests for new RemoveWorkflows signature.
pkg/cli/add_wizard_command.go Updates --dir flag help text to “Workflow directory …”.
pkg/cli/add_integration_test.go Adjusts integration test to pass full workflow dir path.
pkg/cli/add_command.go Updates --dir help text and changes dir resolution to treat it as a full path.
cmd/gh-aw/main.go Adds --dir flag to remove, registers completion, and updates help examples; removes run implementation detail from help.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 10/10 changed files
  • Comments generated: 2

} else {
githubWorkflowsDir = filepath.Join(gitRoot, opts.WorkflowDir)
}
githubWorkflowsDir = filepath.Join(gitRoot, opts.WorkflowDir)
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

--dir is now treated as a general path relative to the git root, but the current validation only rejects absolute paths. After filepath.Clean, values like ../tmp remain relative and filepath.Join(gitRoot, opts.WorkflowDir) will resolve outside the repository, allowing writes/overwrites outside the repo. Please add a traversal guard (e.g., compute the absolute resolved path and ensure it stays under gitRoot, or explicitly reject paths starting with .. after cleaning).

Suggested change
githubWorkflowsDir = filepath.Join(gitRoot, opts.WorkflowDir)
absGitRoot, err := filepath.Abs(gitRoot)
if err != nil {
return fmt.Errorf("failed to resolve git root %s: %w", gitRoot, err)
}
candidateDir, err := filepath.Abs(filepath.Join(absGitRoot, opts.WorkflowDir))
if err != nil {
return fmt.Errorf("failed to resolve workflow directory %s: %w", opts.WorkflowDir, err)
}
relToRoot, err := filepath.Rel(absGitRoot, candidateDir)
if err != nil {
return fmt.Errorf("failed to validate workflow directory %s: %w", opts.WorkflowDir, err)
}
if relToRoot == ".." || strings.HasPrefix(relToRoot, ".."+string(os.PathSeparator)) {
return fmt.Errorf("workflow directory must stay within the repository, got: %s", opts.WorkflowDir)
}
githubWorkflowsDir = candidateDir

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +25
removeLog.Printf("Removing workflows: pattern=%q, keepOrphans=%v, workflowDir=%q", pattern, keepOrphans, workflowDir)
workflowsDir := workflowDir
if workflowsDir == "" {
workflowsDir = getWorkflowsDir()
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

Now that RemoveWorkflows accepts a custom workflowDir, the implementation uses that directory for removing workflow files but still previews/cleans orphaned includes using helpers that default to hard-coded .github/workflows (e.g., previewOrphanedIncludesgetMarkdownWorkflowFiles(""), and cleanupOrphanedIncludes hard-codes .github/workflows). This can (a) remove include files from the default directory even when --dir points elsewhere, and (b) fail to clean orphaned includes in the custom directory. Consider threading workflowsDir through the orphan preview/cleanup paths (and updating the “No .github/workflows directory found” message to reference the actual workflowsDir). Also validate workflowDir similarly to other commands (reject absolute paths and traversal outside the repo).

See below for a potential fix:

func validateWorkflowDir(workflowDir string) (string, error) {
	cleaned := filepath.Clean(workflowDir)
	if filepath.IsAbs(cleaned) {
		return "", fmt.Errorf("workflow directory must be a relative path: %q", workflowDir)
	}
	if cleaned == ".." || strings.HasPrefix(cleaned, ".."+string(filepath.Separator)) {
		return "", fmt.Errorf("workflow directory must not traverse outside the repository: %q", workflowDir)
	}

	return cleaned, nil
}

// RemoveWorkflows removes workflows matching a pattern
func RemoveWorkflows(pattern string, keepOrphans bool, workflowDir string) error {
	removeLog.Printf("Removing workflows: pattern=%q, keepOrphans=%v, workflowDir=%q", pattern, keepOrphans, workflowDir)

	var workflowsDir string
	if workflowDir == "" {
		workflowsDir = getWorkflowsDir()
	} else {
		validatedDir, err := validateWorkflowDir(workflowDir)
		if err != nil {
			return err
		}
		workflowsDir = validatedDir
	}

	if _, err := os.Stat(workflowsDir); os.IsNotExist(err) {
		fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No %s directory found.", workflowsDir)))

Copilot uses AI. Check for mistakes.
@github-actions github-actions bot mentioned this pull request Apr 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 77/100

⚠️ Acceptable, with suggestions

Metric Value
New/modified tests analyzed 3
✅ Design tests (behavioral contracts) 2 (67%)
⚠️ Implementation tests (low value) 1 (33%)
Tests with error/edge cases 2 (67%)
Duplicate test clusters 0
Test inflation detected No
🚨 Coding-guideline violations 0

Test Classification Details

Test File Classification Issues Detected
TestAddWorkflowToCustomDir pkg/cli/add_integration_test.go:410 ✅ Design None — verifies new --dir full-path behavioral contract
TestRemoveWorkflows (signature update) pkg/cli/commands_test.go:215 ⚠️ Implementation Stub test; only verifies no error is returned; no path-specific assertions
Table entry: RemoveWorkflows lambda pkg/cli/commands_test.go:388 ✅ Design Tests graceful handling of nonexistent workflow pattern

Flagged Tests — Requires Review

⚠️ TestRemoveWorkflows (pkg/cli/commands_test.go:215)

Classification: Implementation / stub test
Issue: The test simply calls RemoveWorkflows("test-pattern", false, "") and asserts no error. The comment "Should not error since it's a stub implementation" reveals this tests implementation state, not a behavioral contract. The signature change (adding "" as workflowDir) is maintenance-only — the test adds no new behavioral assertions for the new workflowDir parameter.
What design invariant does this test enforce? That the stub doesn't panic — not a production behavioral contract.
What would break if deleted? Only compilation-time signature drift. A behavioral regression in path-handling would not be caught.
Suggested improvement: Now that --dir is a real parameter (not a stub), add a test case that verifies RemoveWorkflows with a non-empty workflowDir resolves the target directory correctly. For example, assert that passing workflowDir = ".github/workflows/custom" causes it to look in that directory rather than the default.


Score Breakdown

behavioral_ratio = (2/3) × 40 = 26.7
edge_case_ratio  = (2/3) × 30 = 20.0
duplication_penalty = 0         → 20.0 pts
inflation_penalty   = 0         → 10.0 pts
─────────────────────────────────────────
Total = 76.7 → 77/100

Test inflation check: 3 lines added to test files vs. ~15 lines in production files → ratio ≈ 0.2:1 (no inflation ✅)

Implementation test ratio: 1/3 = 33.3% — just above the 30% threshold, but the flagged case is a pre-existing stub test receiving a mechanical signature update, not a newly written low-value test.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 3 test cases — all //go:build integration
  • 🟨 JavaScript (*.test.cjs, *.test.js): 0 tests modified

i️ No new test files were added in this PR — all changes are modifications to existing tests.


Verdict

Check passed. The primary test change (TestAddWorkflowToCustomDir) correctly enforces the behavioral contract introduced by this PR — specifically that --dir now requires a full path. The TestRemoveWorkflows stub is a minor concern but represents a pre-existing pattern with only a mechanical signature update applied here; it does not reflect newly written low-quality test coverage.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

Note

🔒 Integrity filter blocked 1 item

The following item were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

🧪 Test quality analysis by Test Quality Sentinel · ● 922.2K ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 77/100. Test quality is acceptable — 33% implementation tests, just above the 30% threshold but the flagged case (TestRemoveWorkflows) is a pre-existing stub test receiving only a mechanical signature update, not newly written low-value test code. The key behavioral change (full-path --dir semantics) is properly covered by TestAddWorkflowToCustomDir.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review all comments

@pelikhan pelikhan merged commit a0803a5 into main Apr 10, 2026
79 of 80 checks passed
@pelikhan pelikhan deleted the copilot/cli-consistency-issues-resolution branch April 10, 2026 16:13
Copilot stopped work on behalf of pelikhan due to an error April 10, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[cli-consistency] CLI Consistency Issues - 2026-04-10

3 participants