Skip to content

feat: add --exclude flag to logs command and MCP tool to skip specified workflows#28452

Closed
Copilot wants to merge 6 commits intomainfrom
copilot/update-logs-command-exclude-workflows
Closed

feat: add --exclude flag to logs command and MCP tool to skip specified workflows#28452
Copilot wants to merge 6 commits intomainfrom
copilot/update-logs-command-exclude-workflows

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 25, 2026

Summary

Adds a new --exclude flag to the gh aw logs command and a corresponding exclude_workflows parameter to the MCP logs tool, allowing users to skip specified workflows when downloading logs.

Excluded runs are filtered before artifact download, which avoids unnecessary GitHub API requests and artifact download network traffic for the excluded workflows.

Changes

  • pkg/cli/logs_command.go: New --exclude (StringSlice) flag. Supports comma-separated values or repeated --exclude flags. Calls DownloadWorkflowLogsWithOptions directly with named fields for clarity.
  • pkg/cli/logs_orchestrator.go: Introduces DownloadWorkflowLogsOptions struct grouping all parameters. DownloadWorkflowLogsWithOptions is the primary implementation; DownloadWorkflowLogs is a thin backward-compatible wrapper. The resolved exclude list is applied to each batch of runs before downloadRunArtifactsConcurrent is called.
  • pkg/cli/logs_utils.go: Two helpers:
    • resolveExcludeWorkflows: loads workflow.GetAllWorkflows() once and builds an in-memory map (keyed by display name and workflow ID, case-insensitive) to batch-resolve all excludes without repeated filesystem scans. Falls back to keeping the raw value when resolution fails.
    • isWorkflowExcluded: case-insensitive comparison against both the display name and a slugified form (spaces→hyphens), so --exclude weekly-research correctly matches a run named "Weekly Research" even when no local lock files are present.
  • pkg/cli/mcp_tools_privileged.go: New exclude_workflows []string field in the logsArgs struct with JSON schema description. Passes --exclude to the CLI subprocess.

Usage

# Exclude a single workflow by ID
gh aw logs --exclude weekly-research

# Exclude by display name
gh aw logs --exclude "Weekly Research"

# Exclude multiple workflows (comma-separated)
gh aw logs --exclude ci-tests,nightly-build

# Works with other filters
gh aw logs --start-date -1w --exclude ci-tests --engine copilot

Tests

  • TestLogsCommandExcludeFlag: verifies the flag is registered with correct type and default
  • TestIsWorkflowExcluded: table-driven tests for exact match, case-insensitive match, slug match (ID matching display name), no match, empty excludes, and multiple excludes

Copilot AI requested a review from pelikhan April 25, 2026 12:20
@pelikhan pelikhan marked this pull request as ready for review April 25, 2026 12:23
Copilot AI review requested due to automatic review settings April 25, 2026 12:23
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

Adds workflow-exclusion support to the gh aw logs command and the MCP logs tool so users can skip specified workflows before downloading artifacts, reducing unnecessary API calls and network traffic.

Changes:

  • Introduces --exclude (StringSlice) to gh aw logs and threads the value through to the log download orchestrator.
  • Adds exclude resolution + matching helpers and applies the exclude filter before artifact downloads.
  • Extends the MCP privileged logs tool schema with exclude_workflows and forwards it to the CLI.
Show a summary per file
File Description
pkg/cli/logs_command.go Adds --exclude flag, examples, and passes excludes into DownloadWorkflowLogs.
pkg/cli/logs_orchestrator.go Adds excludeWorkflows parameter and filters runs before downloading artifacts.
pkg/cli/logs_utils.go Adds helpers to resolve exclude inputs and check exclusion matches.
pkg/cli/mcp_tools_privileged.go Adds exclude_workflows to MCP schema and forwards as --exclude to CLI subprocess.
pkg/cli/logs_command_test.go Adds tests for --exclude flag registration and isWorkflowExcluded.
pkg/cli/logs_json_stderr_order_test.go Updates DownloadWorkflowLogs calls for new parameter.
pkg/cli/logs_ci_scenario_test.go Updates DownloadWorkflowLogs calls for new parameter.
pkg/cli/context_cancellation_test.go Updates DownloadWorkflowLogs calls for new parameter.
pkg/cli/logs_download_test.go Updates DownloadWorkflowLogs calls for new parameter.

Copilot's findings

Tip

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

  • Files reviewed: 9/9 changed files
  • Comments generated: 3

Comment thread pkg/cli/logs_utils.go
Comment on lines +122 to +126
func isWorkflowExcluded(workflowName string, excludes []string) bool {
lowerName := strings.ToLower(workflowName)
for _, ex := range excludes {
if strings.ToLower(ex) == lowerName {
return true
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

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

isWorkflowExcluded only compares the run's WorkflowName to the exclude list. If resolveExcludeWorkflows fails to resolve an ID (e.g., when running with --repo and no local .lock.yml files), the raw ID (like weekly-research) will never match WorkflowRun.WorkflowName (which is a display name), so --exclude <id> won't work in the exact scenario the helper claims to support. Consider matching excludes against both the display name and a normalized workflow ID derived from WorkflowRun.WorkflowPath (e.g., basename normalized by stripping .lock.yml/.md), and/or normalizing exclude inputs similarly.

Copilot uses AI. Check for mistakes.
Comment thread pkg/cli/logs_utils.go
Comment on lines +97 to +107
resolved := make([]string, 0, len(excludes))
for _, name := range excludes {
name = strings.TrimSpace(name)
if name == "" {
continue
}
// Try to resolve to canonical display name via lock files.
displayName, err := workflow.FindWorkflowName(name)
if err == nil && displayName != "" {
logsUtilsLog.Printf("Resolved exclude workflow '%s' -> '%s'", name, displayName)
resolved = append(resolved, displayName)
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

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

resolveExcludeWorkflows calls workflow.FindWorkflowName inside the loop, and FindWorkflowName scans/parses workflow lock files (glob + read) when resolving. With multiple --exclude entries this becomes repeated filesystem work. Consider loading workflows once (e.g., via workflow.GetAllWorkflows) and resolving all excludes against an in-memory map, or adding a batch-resolve helper in pkg/workflow to avoid re-scanning files per exclude value.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +39
func DownloadWorkflowLogs(ctx context.Context, workflowName string, count int, startDate, endDate, outputDir, engine, ref string, beforeRunID, afterRunID int64, repoOverride string, verbose bool, toolGraph bool, noStaged bool, firewallOnly bool, noFirewall bool, parse bool, jsonOutput bool, timeout int, summaryFile string, safeOutputType string, filteredIntegrity bool, train bool, format string, artifactSets []string, excludeWorkflows []string) error {
logsOrchestratorLog.Printf("Starting workflow log download: workflow=%s, count=%d, startDate=%s, endDate=%s, outputDir=%s, summaryFile=%s, safeOutputType=%s, filteredIntegrity=%v, train=%v, format=%s, artifactSets=%v, excludeWorkflows=%v", workflowName, count, startDate, endDate, outputDir, summaryFile, safeOutputType, filteredIntegrity, train, format, artifactSets, excludeWorkflows)
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

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

DownloadWorkflowLogs already has a very large parameter list, and adding excludeWorkflows increases call-site churn and makes the API harder to use correctly. Consider introducing an options struct (e.g., DownloadWorkflowLogsOptions) and passing a single argument, keeping the current signature as a thin backward-compatible wrapper if needed.

See below for a potential fix:

// DownloadWorkflowLogsOptions groups the configuration for downloading and analyzing workflow logs.
type DownloadWorkflowLogsOptions struct {
	WorkflowName      string
	Count             int
	StartDate         string
	EndDate           string
	OutputDir         string
	Engine            string
	Ref               string
	BeforeRunID       int64
	AfterRunID        int64
	RepoOverride      string
	Verbose           bool
	ToolGraph         bool
	NoStaged          bool
	FirewallOnly      bool
	NoFirewall        bool
	Parse             bool
	JSONOutput        bool
	Timeout           int
	SummaryFile       string
	SafeOutputType    string
	FilteredIntegrity bool
	Train             bool
	Format            string
	ArtifactSets      []string
	ExcludeWorkflows  []string
}

// DownloadWorkflowLogs downloads and analyzes workflow logs with metrics.
// This wrapper preserves the existing call signature for backward compatibility.
func DownloadWorkflowLogs(ctx context.Context, workflowName string, count int, startDate, endDate, outputDir, engine, ref string, beforeRunID, afterRunID int64, repoOverride string, verbose bool, toolGraph bool, noStaged bool, firewallOnly bool, noFirewall bool, parse bool, jsonOutput bool, timeout int, summaryFile string, safeOutputType string, filteredIntegrity bool, train bool, format string, artifactSets []string, excludeWorkflows []string) error {
	return DownloadWorkflowLogsWithOptions(ctx, DownloadWorkflowLogsOptions{
		WorkflowName:      workflowName,
		Count:             count,
		StartDate:         startDate,
		EndDate:           endDate,
		OutputDir:         outputDir,
		Engine:            engine,
		Ref:               ref,
		BeforeRunID:       beforeRunID,
		AfterRunID:        afterRunID,
		RepoOverride:      repoOverride,
		Verbose:           verbose,
		ToolGraph:         toolGraph,
		NoStaged:          noStaged,
		FirewallOnly:      firewallOnly,
		NoFirewall:        noFirewall,
		Parse:             parse,
		JSONOutput:        jsonOutput,
		Timeout:           timeout,
		SummaryFile:       summaryFile,
		SafeOutputType:    safeOutputType,
		FilteredIntegrity: filteredIntegrity,
		Train:             train,
		Format:            format,
		ArtifactSets:      artifactSets,
		ExcludeWorkflows:  excludeWorkflows,
	})
}

// DownloadWorkflowLogsWithOptions downloads and analyzes workflow logs with metrics using an options struct.
func DownloadWorkflowLogsWithOptions(ctx context.Context, opts DownloadWorkflowLogsOptions) error {
	workflowName := opts.WorkflowName
	count := opts.Count
	startDate := opts.StartDate
	endDate := opts.EndDate
	outputDir := opts.OutputDir
	engine := opts.Engine
	ref := opts.Ref
	beforeRunID := opts.BeforeRunID
	afterRunID := opts.AfterRunID
	repoOverride := opts.RepoOverride
	verbose := opts.Verbose
	toolGraph := opts.ToolGraph
	noStaged := opts.NoStaged
	firewallOnly := opts.FirewallOnly
	noFirewall := opts.NoFirewall
	parse := opts.Parse
	jsonOutput := opts.JSONOutput
	timeout := opts.Timeout
	summaryFile := opts.SummaryFile
	safeOutputType := opts.SafeOutputType
	filteredIntegrity := opts.FilteredIntegrity
	train := opts.Train
	format := opts.Format
	artifactSets := opts.ArtifactSets
	excludeWorkflows := opts.ExcludeWorkflows

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 65/100

⚠️ Acceptable, with suggestions

Metric Value
New/modified tests analyzed 2 new test functions
✅ Design tests (behavioral contracts) 1 (50%)
⚠️ Implementation tests (low value) 1 (50%)
Tests with error/edge cases 1 (50%)
Duplicate test clusters 0
Test inflation detected No (0.78:1 ratio)
🚨 Coding-guideline violations None

i️ 7 other modified tests had signature-only changes (adding a nil parameter) and were not re-scored.


Test Classification Details

View All Test Classifications
Test File Classification Notes
TestLogsCommandExcludeFlag pkg/cli/logs_command_test.go:265 ⚠️ Implementation Verifies flag registration metadata; no behavioral coverage of the exclude mechanism
TestIsWorkflowExcluded pkg/cli/logs_command_test.go:275 ✅ Design Table-driven, 6 cases covering exact match, case-insensitivity, nil input, and multi-value lists

Flagged Tests — Requires Review

⚠️ TestLogsCommandExcludeFlag (pkg/cli/logs_command_test.go:265)

Classification: Implementation test

Issue: The test verifies only that the --exclude flag was registered with the expected type and default value. It does not exercise what the flag does — i.e., it does not verify that passing --exclude "My Workflow" actually skips that workflow when downloading logs.

What design invariant does this test enforce? Only that the CLI declaration contains an --exclude flag of type stringSlice. This is observable during manual testing and would be caught immediately by a compilation error if the flag were removed.

What would break if deleted? Nothing behavioral — the flag's existence is already covered implicitly by every test that calls DownloadWorkflowLogs with the new parameter, and explicitly by TestIsWorkflowExcluded testing the underlying helper.

Suggested improvement: Extend the test to call the command with --exclude "SomeWorkflow" and verify the resolved config carries the correct exclude list, or (better) write an integration-style test that passes excludeWorkflows values to DownloadWorkflowLogs and asserts that matching workflow names are skipped in the output/run list. Even a basic check like:

cmd.SetArgs([]string{"--exclude", "Weekly Research"})
_ = cmd.Execute()
// assert that the resolved config.ExcludeWorkflows == ["Weekly Research"]

would elevate this to a behavioral contract test.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 2 tests — unit (//go:build !integration)
  • 🟨 JavaScript (*.test.cjs, *.test.js): 0 tests

Verdict

Check failed. 50% of new tests are classified as low-value implementation tests (threshold: 30%). Please review the flagged test above and add behavioral coverage for the --exclude flag's actual filtering effect.


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

References: §24930818864

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

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: 65/100. 50% of new tests are classified as low-value implementation tests, exceeding the 30% threshold. TestLogsCommandExcludeFlag only verifies flag registration metadata — please add a test that exercises the actual exclude filtering behavior (e.g., passing --exclude values and asserting that matching workflows are skipped). See the comment above for a suggested improvement.

…command

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Commit pushed: dae6135

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (159 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

AI has analyzed the PR diff and generated a draft ADR to help you get started:

📄 Draft ADR: docs/adr/28452-workflow-exclusion-filter-for-logs-command.md

What to do next

  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-28452: Pre-Download Workflow Exclusion Filter for the Logs Command

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.


📋 Draft ADR Preview

Title: ADR-28452: Pre-Download Workflow Exclusion Filter for the Logs Command

Key decision: Add a --exclude (StringSlice) flag to the logs CLI command and exclude_workflows field to the MCP logs tool. Excluded workflows are resolved via lock files and filtered before artifact download to avoid unnecessary API requests and bandwidth.

Alternatives captured in the draft:

  • Post-download filtering (rejected: wastes API quota before discarding results)
  • Deny-list configuration file (deferred: adds scope beyond immediate need)
  • Negating the existing --workflow flag with a ! prefix (rejected: poor ergonomics)
📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 28452-workflow-exclusion-filter-for-logs-command.md for PR #28452).

🔒 This PR cannot merge until an ADR is linked in the PR body.

References: §24930818842

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · ● 140.7K ·

@github-actions github-actions Bot mentioned this pull request Apr 25, 2026
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot merge main and recompile

Review all comments

Copilot AI and others added 2 commits April 25, 2026 12:53
…ommand-exclude-workflows

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…ug matching, merge main

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ec5f3e7a-405a-4d8f-9118-d136c60250b0

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 25, 2026

@copilot merge main and recompile

Review all comments

Done (commit 4c4099c). Merged main and recompiled. Also addressed all three reviewer comments:

  • Options struct (DownloadWorkflowLogsOptions): DownloadWorkflowLogs is now a thin wrapper; logs_command.go calls DownloadWorkflowLogsWithOptions directly.
  • Batch resolve: resolveExcludeWorkflows calls workflow.GetAllWorkflows() once, builds an in-memory map keyed by display name and workflow ID (case-insensitive), and resolves all excludes against it — no repeated filesystem scans.
  • Slug matching: isWorkflowExcluded now also compares a slugified form of the display name (spaces→hyphens) against each exclude entry, so --exclude weekly-research matches a run named "Weekly Research" even when no local lock files are present.

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 (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 n_mode.go n_pins.go n_pins_integratirev-parse n_pi�� json&#39; --ignore-path ../../../.pr**/*.json n_reference.go n_reference_test.go n_resolver.go n_resolver_test.-C n_sha_checker.go/home/REDACTED/work/gh-aw/gh-aw/.github/workflows n_sha_checker_inrev-parse (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 g/workflow/actio-C g/workflow/actio/home/REDACTED/work/gh-aw/gh-aw/.github/workflows g/workflow/actioconfig g/wo�� &#39;**/*.ts&#39; &#39;**/*.remote.origin.url g/workflow/action_reference.go r: $owner, name: $name) { hasDiscussionsEnabled } } g/workflow/actioinfocmp g/workflow/actio-1 g/workflow/actioxterm-color g/workflow/action_sha_checker_inrev-parse (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, .object.type] | @tsv (http block)
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq [.object.sha, .object.type] | @tsv h ../../../.pret.prettierignore (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .default_branch */*.ts&#39; &#39;**/*.json&#39; --ignore-path ../../../.prettierignore k/gh-aw/gh-aw/pkg/workflow/resolve.go 64/bin/sh (http block)
  • https://api.github.com/repos/github/gh-aw/actions/workflows
    • 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)

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

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot merge main and recompile

…ommand-exclude-workflows

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 25, 2026

@copilot merge main and recompile

Done (commit 1b0a358). Merged main and recompiled all 202 workflow files.

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 (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 -pack /home/REDACTED/work/gh-aw/gh-aw/cmd/gh-aw/main.go (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, .object.type] | @tsv (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .default_branch k/_temp/runtime-logs/user.env (http block)

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

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.

3 participants