From 7cf03ba385197274a16d7dbd23f0b0aba50d1bdf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:56:56 +0000 Subject: [PATCH 1/3] Initial plan From afd106b063b9d459d493ea4d36d327b59a89a5a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 00:13:48 +0000 Subject: [PATCH 2/3] Fix: deprecate needs.activation.outputs.* in workflow markdown, update docs to use steps.sanitized.outputs.* - Add deprecation warning in expression_extraction.go when old form is used in compilation - Update all user-facing docs to use steps.sanitized.outputs.{text,title,body} - Update workflow template files, skill files, and AGENTS.md - templating.md now shows the old form as deprecated with a caution block Fixes #" Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- .github/aw/create-agentic-workflow.md | 6 +-- .github/aw/github-agentic-workflows.md | 20 +++++----- .github/aw/update-agentic-workflow.md | 2 +- .../instructions/workflow-performance.md | 14 +++---- AGENTS.md | 37 ++++++++----------- docs/slides/index.md | 2 +- .../examples/multi-repo/issue-tracking.md | 4 +- .../content/docs/guides/editing-workflows.md | 4 +- docs/src/content/docs/patterns/chat-ops.md | 4 +- docs/src/content/docs/patterns/issue-ops.md | 4 +- .../docs/reference/command-triggers.md | 10 ++--- .../docs/reference/custom-safe-outputs.md | 4 +- docs/src/content/docs/reference/templating.md | 35 +++++------------- docs/src/content/docs/reference/triggers.md | 2 +- .../docs/troubleshooting/common-issues.md | 4 +- pkg/cli/workflows/test-all.md | 2 +- pkg/workflow/expression_extraction.go | 9 ++++- skills/developer/SKILL.md | 6 +-- skills/dictation/SKILL.md | 6 +-- 19 files changed, 80 insertions(+), 95 deletions(-) diff --git a/.github/aw/create-agentic-workflow.md b/.github/aw/create-agentic-workflow.md index 475286c516a..20b603d907a 100644 --- a/.github/aw/create-agentic-workflow.md +++ b/.github/aw/create-agentic-workflow.md @@ -539,7 +539,7 @@ These resources contain workflow patterns, best practices, safe outputs, and per - Prefer `safe-outputs` (`create-issue`, `add-comment`, `create-pull-request`, `create-pull-request-review-comment`, `update-issue` for editing, `close-issue` for closing, `dispatch-workflow`) over granting write perms. - For custom write operations to external services (email, Slack, webhooks), use `safe-outputs.jobs:` to create custom safe output jobs. - Constrain `network:` to the minimum required ecosystems/domains. - - Use sanitized expressions (`${{ needs.activation.outputs.text }}`) instead of raw event text. + - Use sanitized expressions (`${{ steps.sanitized.outputs.text }}`) instead of raw event text. - **Emphasize human agency in workflow prompts**: - When writing prompts that report on repository activity (commits, PRs, issues), always attribute bot activity to humans - **@github-actions[bot]** and **@Copilot** are tools triggered by humans - workflows should identify who triggered, reviewed, or merged their actions @@ -571,14 +571,14 @@ safe-outputs: # Deploy Preview Deploy a preview environment for this pull request. The caller wrote: -"${{ needs.activation.outputs.text }}" +"${{ steps.sanitized.outputs.text }}" ``` **Tradeoffs:** - ✅ Works across issues, PRs, and all comment types (configurable via `events:`) - ✅ Natural to invoke — users type `/command` in any comment - ✅ Supports multiple command aliases in one workflow (`name: ["deploy", "redeploy"]`) -- ✅ The triggering comment text is available as context via `needs.activation.outputs.text` +- ✅ The triggering comment text is available as context via `steps.sanitized.outputs.text` - ⚠️ Less discoverable — users must know the command name exists - ⚠️ Cannot be triggered without writing a comment (no label-based invocation) diff --git a/.github/aw/github-agentic-workflows.md b/.github/aw/github-agentic-workflows.md index d6e53000b6c..0f99584c195 100644 --- a/.github/aw/github-agentic-workflows.md +++ b/.github/aw/github-agentic-workflows.md @@ -1575,17 +1575,17 @@ Use GitHub Actions context expressions throughout the workflow content. **Note: - **`${{ github.workspace }}`** - The default working directory on the runner for steps #### Special Pattern Expressions -- **`${{ needs.* }}`** - Any outputs from previous jobs (e.g., `${{ needs.activation.outputs.text }}`) +- **`${{ needs.* }}`** - Any outputs from previous jobs (e.g., `${{ steps.sanitized.outputs.text }}`) - **`${{ steps.* }}`** - Any outputs from previous steps (e.g., `${{ steps.my-step.outputs.result }}`) - **`${{ github.event.inputs.* }}`** - Any workflow inputs when triggered by workflow_dispatch (e.g., `${{ github.event.inputs.environment }}`) All other expressions are disallowed. -### Sanitized Context Text (`needs.activation.outputs.text`) +### Sanitized Context Text (`steps.sanitized.outputs.text`) -**RECOMMENDED**: Use `${{ needs.activation.outputs.text }}` instead of individual `github.event` fields for accessing issue/PR content. +**RECOMMENDED**: Use `${{ steps.sanitized.outputs.text }}` instead of individual `github.event` fields for accessing issue/PR content. -The `needs.activation.outputs.text` value provides automatically sanitized content based on the triggering event: +The `steps.sanitized.outputs.text` value provides automatically sanitized content based on the triggering event: - **Issues**: `title + "\n\n" + body` - **Pull Requests**: `title + "\n\n" + body` @@ -1605,7 +1605,7 @@ The `needs.activation.outputs.text` value provides automatically sanitized conte **Example Usage:** ```markdown # RECOMMENDED: Use sanitized context text -Analyze this content: "${{ needs.activation.outputs.text }}" +Analyze this content: "${{ steps.sanitized.outputs.text }}" # Less secure alternative (use only when specific fields are needed) Issue number: ${{ github.event.issue.number }} @@ -1614,7 +1614,7 @@ Repository: ${{ github.repository }} ### Accessing Individual Context Fields -While `needs.activation.outputs.text` is recommended for content access, you can still use individual context fields for metadata: +While `steps.sanitized.outputs.text` is recommended for content access, you can still use individual context fields for metadata: ### Security Validation @@ -1625,12 +1625,12 @@ Expression safety is automatically validated during compilation. If unauthorized # Valid expressions - RECOMMENDED: Use sanitized context text for security Analyze issue #${{ github.event.issue.number }} in repository ${{ github.repository }}. -The issue content is: "${{ needs.activation.outputs.text }}" +The issue content is: "${{ steps.sanitized.outputs.text }}" # Alternative approach using individual fields (less secure) The issue was created by ${{ github.actor }} with title: "${{ github.event.issue.title }}" -Using output from previous task: "${{ needs.activation.outputs.text }}" +Using output from previous task: "${{ steps.sanitized.outputs.text }}" Deploy to environment: "${{ github.event.inputs.environment }}" @@ -2033,7 +2033,7 @@ safe-outputs: # Helper Bot -Respond to /helper-bot mentions with helpful information related to ${{ github.repository }}. The request is "${{ needs.activation.outputs.text }}". +Respond to /helper-bot mentions with helpful information related to ${{ github.repository }}. The request is "${{ steps.sanitized.outputs.text }}". ``` ### Workflow Improvement Bot @@ -2298,7 +2298,7 @@ Use `gh aw fix --write` to automatically migrate deprecated configurations: 9. **Use specific tool permissions** rather than broad access 10. **Monitor costs with `gh aw logs`** to track AI model usage and expenses 11. **Use `--engine` filter** in logs command to analyze specific AI engine performance -12. **Prefer sanitized context text** - Use `${{ needs.activation.outputs.text }}` instead of raw `github.event` fields for security +12. **Prefer sanitized context text** - Use `${{ steps.sanitized.outputs.text }}` instead of raw `github.event` fields for security 13. **Run security scanners** - Use `--actionlint`, `--zizmor`, and `--poutine` flags to scan compiled workflows for security issues, code quality, and supply chain risks ## Validation diff --git a/.github/aw/update-agentic-workflow.md b/.github/aw/update-agentic-workflow.md index 5158880220d..acad8f6125a 100644 --- a/.github/aw/update-agentic-workflow.md +++ b/.github/aw/update-agentic-workflow.md @@ -273,7 +273,7 @@ When updating workflows, maintain security: - Default to `permissions: read-all` and expand only if necessary - Prefer `safe-outputs` over granting write permissions - Constrain `network:` to the minimum required ecosystems/domains -- Use sanitized expressions (`${{ needs.activation.outputs.text }}`) +- Use sanitized expressions (`${{ steps.sanitized.outputs.text }}`) ## Update Workflow Process diff --git a/.github/copilot/instructions/workflow-performance.md b/.github/copilot/instructions/workflow-performance.md index cda6bd853c0..26a82622618 100644 --- a/.github/copilot/instructions/workflow-performance.md +++ b/.github/copilot/instructions/workflow-performance.md @@ -47,7 +47,7 @@ on: types: [opened] --- -# Analyze issue: "${{ needs.activation.outputs.text }}" +# Analyze issue: "${{ steps.sanitized.outputs.text }}" ``` **Benefits**: @@ -95,7 +95,7 @@ tools: ```markdown Repository: ${{ github.repository }} Issue Number: ${{ github.event.issue.number }} -Issue Content: "${{ needs.activation.outputs.text }}" +Issue Content: "${{ steps.sanitized.outputs.text }}" Analyze the above issue (content already provided). ``` @@ -158,7 +158,7 @@ on: issues # Issue Triage for #${{ github.event.issue.number }} -**Issue Content**: "${{ needs.activation.outputs.text }}" +**Issue Content**: "${{ steps.sanitized.outputs.text }}" Tasks: 1. Categorize issue type (bug/feature/question) @@ -352,11 +352,11 @@ Annual cost (500 runs): ~$75 Get the full issue details, then summarize the description. ``` -**Problem**: Fetches large response just to extract what's already in `needs.activation.outputs.text`. +**Problem**: Fetches large response just to extract what's already in `steps.sanitized.outputs.text`. **Solution**: ```markdown -Issue: "${{ needs.activation.outputs.text }}" +Issue: "${{ steps.sanitized.outputs.text }}" Summarize the above issue content. ``` @@ -409,7 +409,7 @@ grep -A5 "token_usage" logs/*.log ``` **Solution**: -1. Replace API calls with `needs.activation.outputs.text` +1. Replace API calls with `steps.sanitized.outputs.text` 2. Reduce tool permissions to minimum 3. Add explicit "don't re-fetch" instructions @@ -446,4 +446,4 @@ grep "turn" logs/*.log | wc -l - Issue #1728: Token usage optimization case study (481k → 150k) - Issue #2012: Workflow performance analysis patterns - `gh aw logs` documentation: Analyzing workflow execution -- Sanitized context text: Using `needs.activation.outputs.text` +- Sanitized context text: Using `steps.sanitized.outputs.text` diff --git a/AGENTS.md b/AGENTS.md index 9b04275a5b2..709a87a3109 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -772,40 +772,35 @@ go test -race ./... ### Expression Transformations in Workflows -**Automatic Activation Output Transformations:** +**Deprecated: `needs.activation.outputs.*` in Workflow Markdown** -The compiler automatically transforms certain `needs.activation.outputs.*` expressions to `steps.sanitized.outputs.*` for compatibility with the activation job context. +Use `steps.sanitized.outputs.text/title/body` directly in workflow markdown prompts: -**Why this transformation occurs:** +- `${{ steps.sanitized.outputs.text }}` — sanitized full context +- `${{ steps.sanitized.outputs.title }}` — sanitized issue/PR title +- `${{ steps.sanitized.outputs.body }}` — sanitized issue/PR body -The prompt is generated **within the activation job**, which means it cannot reference its own `needs.activation.*` outputs (a job cannot reference its own needs outputs in GitHub Actions). The compiler automatically rewrites these expressions to reference the `sanitized` step, which computes sanitized versions of the triggering content. +**Why the `steps.sanitized.*` form is required:** -**Transformations:** +The prompt is generated **within the activation job**, which means it cannot reference its own `needs.activation.*` outputs (a job cannot reference its own needs outputs in GitHub Actions). The `sanitized` step within the activation job computes sanitized versions of the triggering content. + +**Backward compatibility:** + +The compiler still accepts the old form (`needs.activation.outputs.text/title/body`) and automatically rewrites it to `steps.sanitized.outputs.*`, but emits a deprecation warning. Use `steps.sanitized.outputs.*` directly in all new and updated workflows. + +**Old deprecated form → new correct form:** - `needs.activation.outputs.text` → `steps.sanitized.outputs.text` - `needs.activation.outputs.title` → `steps.sanitized.outputs.title` - `needs.activation.outputs.body` → `steps.sanitized.outputs.body` **Important notes:** -- Only `text`, `title`, and `body` outputs are transformed -- Other activation outputs (`comment_id`, `comment_repo`, `slash_command`) are NOT transformed -- Transformation uses word boundary checking to prevent incorrect partial matches (e.g., `text_custom` is not transformed) -- This is particularly important for runtime-import, where markdown can change without recompilation - -**Example:** - -```markdown -Analyze this content: "${{ needs.activation.outputs.text }}" -``` - -Is automatically transformed to: - -```markdown -Analyze this content: "${{ steps.sanitized.outputs.text }}" -``` +- Only `text`, `title`, and `body` outputs are affected; use `needs.activation.outputs.*` for `comment_id`, `comment_repo`, `slash_command` etc. in _downstream_ jobs +- Backward-compat transformation uses word boundary checking (e.g., `text_custom` is not transformed) **Implementation:** - Transformation happens in `pkg/workflow/expression_extraction.go::transformActivationOutputs()` - Applied during expression extraction from markdown +- Deprecation warnings emitted to stderr during compilation - Transformations are logged for debugging when `DEBUG=workflow:expression_extraction` ### YAML Library Usage diff --git a/docs/slides/index.md b/docs/slides/index.md index bcfbc533fc5..592c8cb2187 100644 --- a/docs/slides/index.md +++ b/docs/slides/index.md @@ -345,7 +345,7 @@ safe-outputs: --- # RECOMMENDED: Use sanitized context Analyze this issue content (safely sanitized): -"${{ needs.activation.outputs.text }}" +"${{ steps.sanitized.outputs.text }}" Metadata: - Issue #${{ github.event.issue.number }} diff --git a/docs/src/content/docs/examples/multi-repo/issue-tracking.md b/docs/src/content/docs/examples/multi-repo/issue-tracking.md index 5fc77263549..39662389a53 100644 --- a/docs/src/content/docs/examples/multi-repo/issue-tracking.md +++ b/docs/src/content/docs/examples/multi-repo/issue-tracking.md @@ -45,7 +45,7 @@ tracking issue in the central tracker. **Original issue:** ${{ github.event.issue.html_url }} **Issue number:** ${{ github.event.issue.number }} -**Content:** "${{ needs.activation.outputs.text }}" +**Content:** "${{ steps.sanitized.outputs.text }}" Create tracking issue with link to original, component identifier, summary, suggested priority, and labels `from-component-alpha` and `tracking-issue`. ``` @@ -179,7 +179,7 @@ safe-outputs: Analyze new issues and create tracking issues in appropriate repositories. **Original issue:** ${{ github.event.issue.html_url }} -**Content:** "${{ needs.activation.outputs.text }}" +**Content:** "${{ steps.sanitized.outputs.text }}" Analyze issue severity and route to appropriate tracker: security issues to `myorg/security-tracker`, features to `myorg/feature-tracker`, bugs to `myorg/bug-tracker`, or infrastructure to `myorg/ops-tracker`. Include original link, triage reasoning, priority, affected components, and SLA targets. ``` diff --git a/docs/src/content/docs/guides/editing-workflows.md b/docs/src/content/docs/guides/editing-workflows.md index 710c92e3eb8..dec3b5c3b88 100644 --- a/docs/src/content/docs/guides/editing-workflows.md +++ b/docs/src/content/docs/guides/editing-workflows.md @@ -156,7 +156,7 @@ Read issue #${{ github.event.issue.number }} in repository ${{ github.repository Issue title: "${{ github.event.issue.title }}" -Use sanitized content: "${{ needs.activation.outputs.text }}" +Use sanitized content: "${{ steps.sanitized.outputs.text }}" Actor: ${{ github.actor }} Repository: ${{ github.repository }} @@ -173,7 +173,7 @@ Arbitrary expressions are blocked for security. This will fail at runtime: Run this command: ${{ github.event.comment.body }} ``` -Use `needs.activation.outputs.text` for sanitized user input instead. +Use `steps.sanitized.outputs.text` for sanitized user input instead. ## Quick Reference diff --git a/docs/src/content/docs/patterns/chat-ops.md b/docs/src/content/docs/patterns/chat-ops.md index c0d68684cc3..18753b4139e 100644 --- a/docs/src/content/docs/patterns/chat-ops.md +++ b/docs/src/content/docs/patterns/chat-ops.md @@ -75,11 +75,11 @@ Customize access with the `roles:` configuration. Use `roles: [admin, maintainer ## Accessing Context Information -Access sanitized event context through `needs.activation.outputs.text`: +Access sanitized event context through `steps.sanitized.outputs.text`: ```aw wrap # Reference the sanitized text in your workflow: -Analyze this content: "${{ needs.activation.outputs.text }}" +Analyze this content: "${{ steps.sanitized.outputs.text }}" ``` Sanitization filters unauthorized mentions, malicious links, and excessive content while preserving essential information. diff --git a/docs/src/content/docs/patterns/issue-ops.md b/docs/src/content/docs/patterns/issue-ops.md index 1a448eeba6c..49b493a6c66 100644 --- a/docs/src/content/docs/patterns/issue-ops.md +++ b/docs/src/content/docs/patterns/issue-ops.md @@ -42,10 +42,10 @@ safe-outputs: ## Accessing Issue Context -Access sanitized issue content through `needs.activation.outputs.text`, which combines title and description while removing security risks (@mentions, URIs, injections): +Access sanitized issue content through `steps.sanitized.outputs.text`, which combines title and description while removing security risks (@mentions, URIs, injections): ```yaml wrap -Analyze this issue: "${{ needs.activation.outputs.text }}" +Analyze this issue: "${{ steps.sanitized.outputs.text }}" ``` ## Common IssueOps Patterns diff --git a/docs/src/content/docs/reference/command-triggers.md b/docs/src/content/docs/reference/command-triggers.md index d18d4886c76..75e41c940dc 100644 --- a/docs/src/content/docs/reference/command-triggers.md +++ b/docs/src/content/docs/reference/command-triggers.md @@ -117,7 +117,7 @@ tools: When someone mentions /summarize-issue in an issue or comment, analyze and provide a helpful summary. -The current context text is: "${{ needs.activation.outputs.text }}" +The current context text is: "${{ steps.sanitized.outputs.text }}" ``` PR-focused example using event filtering to restrict to pull requests and PR comments: @@ -145,7 +145,7 @@ timeout-minutes: 10 When someone mentions /code-review in a pull request or PR comment, analyze the code changes and provide detailed feedback. -The current context is: "${{ needs.activation.outputs.text }}" +The current context is: "${{ steps.sanitized.outputs.text }}" Review the pull request changes and add helpful review comments on specific lines of code where improvements can be made. @@ -153,10 +153,10 @@ lines of code where improvements can be made. ## Context Text -All workflows access `needs.activation.outputs.text`, which provides **sanitized** context: for issues and PRs, it's `title + "\n\n" + body`; for comments and reviews, it's the body content. +All workflows access `steps.sanitized.outputs.text`, which provides **sanitized** context: for issues and PRs, it's `title + "\n\n" + body`; for comments and reviews, it's the body content. ```aw wrap -# Analyze this content: "${{ needs.activation.outputs.text }}" +# Analyze this content: "${{ steps.sanitized.outputs.text }}" ``` **Why sanitized context?** The sanitized text neutralizes @mentions and bot triggers (like `fixes #123`), protects against XML injection, filters URIs to trusted HTTPS domains, limits content size (0.5MB max, 65k lines), and strips ANSI escape sequences. @@ -164,7 +164,7 @@ All workflows access `needs.activation.outputs.text`, which provides **sanitized **Comparison:** ```aw wrap # RECOMMENDED: Secure sanitized context -Analyze this issue: "${{ needs.activation.outputs.text }}" +Analyze this issue: "${{ steps.sanitized.outputs.text }}" # DISCOURAGED: Raw context values (security risks) Title: "${{ github.event.issue.title }}" diff --git a/docs/src/content/docs/reference/custom-safe-outputs.md b/docs/src/content/docs/reference/custom-safe-outputs.md index 402e8881a73..1a9d29e29f5 100644 --- a/docs/src/content/docs/reference/custom-safe-outputs.md +++ b/docs/src/content/docs/reference/custom-safe-outputs.md @@ -58,7 +58,7 @@ imports: # Issue Notifier -A new issue was opened: "${{ needs.activation.outputs.text }}" +A new issue was opened: "${{ steps.sanitized.outputs.text }}" Summarize the issue and use the slack-notify tool to send a notification. ``` @@ -208,7 +208,7 @@ imports: # Issue Summary to Notion -Analyze the issue: "${{ needs.activation.outputs.text }}" +Analyze the issue: "${{ steps.sanitized.outputs.text }}" Search for the GitHub Issues page in Notion using the read-only Notion tools, then add a summary comment using the notion-add-comment safe-job. ``` diff --git a/docs/src/content/docs/reference/templating.md b/docs/src/content/docs/reference/templating.md index c1b0c040099..8aad584cbd7 100644 --- a/docs/src/content/docs/reference/templating.md +++ b/docs/src/content/docs/reference/templating.md @@ -24,37 +24,22 @@ Agentic workflows restrict expressions in **markdown content** to prevent securi - Run metadata: `github.run_id`, `github.run_number`, `github.job`, `github.workflow` - Pattern expressions: `needs.*`, `steps.*`, `github.event.inputs.*` -### Automatic Expression Transformations +### Activation Outputs -The compiler automatically transforms certain expressions to ensure they work correctly in the activation job context: +Use `steps.sanitized.outputs.text/title/body` in your markdown prompts to access sanitized event content: -**Activation Output Transformations:** -- `needs.activation.outputs.text` → `steps.sanitized.outputs.text` -- `needs.activation.outputs.title` → `steps.sanitized.outputs.title` -- `needs.activation.outputs.body` → `steps.sanitized.outputs.body` +- `steps.sanitized.outputs.text` — sanitized full context (title + body for issues/PRs, body for comments) +- `steps.sanitized.outputs.title` — sanitized title of the triggering issue or PR +- `steps.sanitized.outputs.body` — sanitized body of the triggering issue or PR -**Why this transformation occurs:** +:::caution[Deprecated: `needs.activation.outputs.*`] +Using `${{ needs.activation.outputs.text }}`, `${{ needs.activation.outputs.title }}`, or `${{ needs.activation.outputs.body }}` in workflow markdown is **deprecated**. These expressions still work but produce a deprecation warning during compilation. Use `${{ steps.sanitized.outputs.text }}` etc. directly instead. -The prompt is generated within the activation job, which cannot reference its own `needs.activation.*` outputs (a job cannot reference its own needs outputs in GitHub Actions). Instead, the compiler automatically rewrites these expressions to reference the `sanitized` step within the activation job, which computes sanitized versions of the issue/PR text, title, and body. - -**Example:** - -```markdown -Analyze this content: "${{ needs.activation.outputs.text }}" -``` - -Is automatically transformed during compilation to: - -```markdown -Analyze this content: "${{ steps.sanitized.outputs.text }}" -``` - -This transformation is particularly important for [runtime imports](#runtime-imports), which allow you to edit markdown content without recompilation. The compiler ensures all necessary expressions are available for runtime substitution. - -:::note -Only `text`, `title`, and `body` outputs are transformed. Other activation outputs like `comment_id` and `comment_repo` are not transformed and remain as `needs.activation.outputs.*`. +**Why:** The prompt is generated _inside_ the activation job, which cannot reference its own `needs.activation.*` outputs in GitHub Actions. The compiler automatically rewrites the deprecated form to `steps.sanitized.outputs.*`, but writing the correct form directly is preferred. ::: +Other activation outputs like `comment_id`, `comment_repo`, and `slash_command` are available as `needs.activation.outputs.*` in _downstream_ jobs (not in the markdown prompt itself). + ### Prohibited Expressions All other expressions are disallowed, including `secrets.*`, `env.*`, `vars.*`, and complex functions like `toJson()` or `fromJson()`. diff --git a/docs/src/content/docs/reference/triggers.md b/docs/src/content/docs/reference/triggers.md index da20e621d21..b00fb4d78b8 100644 --- a/docs/src/content/docs/reference/triggers.md +++ b/docs/src/content/docs/reference/triggers.md @@ -191,7 +191,7 @@ safe-outputs: Process the issue and make multiple updates without interference from concurrent modifications. -Context: "${{ needs.activation.outputs.text }}" +Context: "${{ steps.sanitized.outputs.text }}" ``` ### Pull Request Triggers (`pull_request:`) diff --git a/docs/src/content/docs/troubleshooting/common-issues.md b/docs/src/content/docs/troubleshooting/common-issues.md index 3b4ba07ccc1..9248a87254d 100644 --- a/docs/src/content/docs/troubleshooting/common-issues.md +++ b/docs/src/content/docs/troubleshooting/common-issues.md @@ -381,11 +381,11 @@ Run these commands from inside a GHES repository clone — they auto-detect the ### Unauthorized Expression -Use only [allowed expressions](/gh-aw/reference/templating/) (`github.event.issue.number`, `github.repository`, `needs.activation.outputs.text`). Disallowed: `secrets.*`, `env.*`. +Use only [allowed expressions](/gh-aw/reference/templating/) (`github.event.issue.number`, `github.repository`, `steps.sanitized.outputs.text`). Disallowed: `secrets.*`, `env.*`. ### Sanitized Context Empty -`needs.activation.outputs.text` requires issue/PR/comment events (`on: issues:`), not `push:` or similar triggers. +`steps.sanitized.outputs.text` requires issue/PR/comment events (`on: issues:`), not `push:` or similar triggers. ## Build and Test Issues diff --git a/pkg/cli/workflows/test-all.md b/pkg/cli/workflows/test-all.md index a644ebd85d0..0975b23ccf4 100644 --- a/pkg/cli/workflows/test-all.md +++ b/pkg/cli/workflows/test-all.md @@ -161,7 +161,7 @@ You are the **Poem Bot**, a creative AI agent that responds to various GitHub ev - **Triggered by**: GitHub workflow - **Poem Theme**: ${{ env.POEM_THEME }} - **Action Type**: ${{ env.ACTION_TYPE }} -- **Content**: "${{ needs.activation.outputs.text }}" +- **Content**: "${{ steps.sanitized.outputs.text }}" ## Your Mission diff --git a/pkg/workflow/expression_extraction.go b/pkg/workflow/expression_extraction.go index 830480e1879..023e5e69a13 100644 --- a/pkg/workflow/expression_extraction.go +++ b/pkg/workflow/expression_extraction.go @@ -4,10 +4,12 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "os" "regexp" "sort" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" ) @@ -64,11 +66,14 @@ func (e *ExpressionExtractor) ExtractExpressions(markdown string) ([]*Expression // Apply activation output transformation for backward compatibility // This transforms needs.activation.outputs.{text|title|body} to steps.sanitized.outputs.{text|title|body} - // Users should now use steps.sanitized.outputs.* directly, but we keep this transformation - // for backward compatibility with existing workflows. + // Users should now use steps.sanitized.outputs.* directly; this transformation exists only for + // backward compatibility with existing workflows. transformedContent := transformActivationOutputs(content) if transformedContent != content { expressionExtractionLog.Printf("Transformed expression: %s -> %s", content, transformedContent) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage( + fmt.Sprintf("Deprecated expression '${{ %s }}': use '${{ %s }}' instead.", content, transformedContent), + )) content = transformedContent } diff --git a/skills/developer/SKILL.md b/skills/developer/SKILL.md index b3a1e463bce..98135e686d4 100644 --- a/skills/developer/SKILL.md +++ b/skills/developer/SKILL.md @@ -706,10 +706,10 @@ See `scratchpad/template-injection-prevention.md` for detailed analysis and fix ```yaml # SECURE: Use sanitized context output -Analyze this content: "${{ needs.activation.outputs.text }}" +Analyze this content: "${{ steps.sanitized.outputs.text }}" ``` -The `needs.activation.outputs.text` output is automatically sanitized: +The `steps.sanitized.outputs.text` output is automatically sanitized: - @mentions neutralized - Bot triggers protected - XML tags converted to safe format @@ -923,7 +923,7 @@ gh aw compile --strict --actionlint --zizmor --poutine - [ ] No untrusted input in `${{ }}` expressions - [ ] Untrusted data passed via environment variables - [ ] Safe context variables used where possible -- [ ] Sanitized context used (gh-aw: `needs.activation.outputs.text`) +- [ ] Sanitized context used (gh-aw: `steps.sanitized.outputs.text`) #### Shell Scripts - [ ] All variables quoted: `"$VAR"` diff --git a/skills/dictation/SKILL.md b/skills/dictation/SKILL.md index 22c57ecb55d..b44adeeba90 100644 --- a/skills/dictation/SKILL.md +++ b/skills/dictation/SKILL.md @@ -653,9 +653,9 @@ multirepoops my-workflow myorg needs.activation -needs.activation.outputs.body -needs.activation.outputs.text -needs.activation.outputs.title +steps.sanitized.outputs.body +steps.sanitized.outputs.text +steps.sanitized.outputs.title network network.allowed network.firewall From 5c48bbcf98e9db829e831eedd3b6c7ce0460cba1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 01:19:13 +0000 Subject: [PATCH 3/3] Fix: de-dup deprecation warnings and correct needs.* bullet example - Move deprecation warning after the 'already seen' check so each unique deprecated expression only warns once per compilation - Fix misleading example under the needs.* bullet in github-agentic-workflows.md (was showing a steps.* expression; now shows needs.pre_activation.outputs.activated) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/aw/github-agentic-workflows.md | 2 +- pkg/workflow/expression_extraction.go | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/aw/github-agentic-workflows.md b/.github/aw/github-agentic-workflows.md index c624b2f0ca1..9e6a0a9b4c5 100644 --- a/.github/aw/github-agentic-workflows.md +++ b/.github/aw/github-agentic-workflows.md @@ -1575,7 +1575,7 @@ Use GitHub Actions context expressions throughout the workflow content. **Note: - **`${{ github.workspace }}`** - The default working directory on the runner for steps #### Special Pattern Expressions -- **`${{ needs.* }}`** - Any outputs from previous jobs (e.g., `${{ steps.sanitized.outputs.text }}`) +- **`${{ needs.* }}`** - Any outputs from previous jobs (e.g., `${{ needs.pre_activation.outputs.activated }}`) - **`${{ steps.* }}`** - Any outputs from previous steps (e.g., `${{ steps.my-step.outputs.result }}`) - **`${{ github.event.inputs.* }}`** - Any workflow inputs when triggered by workflow_dispatch (e.g., `${{ github.event.inputs.environment }}`) diff --git a/pkg/workflow/expression_extraction.go b/pkg/workflow/expression_extraction.go index 023e5e69a13..a6bfe7b6b1b 100644 --- a/pkg/workflow/expression_extraction.go +++ b/pkg/workflow/expression_extraction.go @@ -71,17 +71,21 @@ func (e *ExpressionExtractor) ExtractExpressions(markdown string) ([]*Expression transformedContent := transformActivationOutputs(content) if transformedContent != content { expressionExtractionLog.Printf("Transformed expression: %s -> %s", content, transformedContent) - fmt.Fprintln(os.Stderr, console.FormatWarningMessage( - fmt.Sprintf("Deprecated expression '${{ %s }}': use '${{ %s }}' instead.", content, transformedContent), - )) content = transformedContent } - // Skip if we've already seen this expression + // Skip if we've already seen this expression (also prevents duplicate deprecation warnings) if _, exists := e.mappings[originalExpr]; exists { continue } + // Emit deprecation warning once per unique deprecated expression + if transformedContent != strings.TrimSpace(match[1]) { + fmt.Fprintln(os.Stderr, console.FormatWarningMessage( + fmt.Sprintf("Deprecated expression '${{ %s }}': use '${{ %s }}' instead.", strings.TrimSpace(match[1]), transformedContent), + )) + } + // Generate environment variable name envVar := e.generateEnvVarName(content)