diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml
new file mode 100644
index 0000000000..5e8604f076
--- /dev/null
+++ b/.github/workflows/smoke-project.lock.yml
@@ -0,0 +1,1382 @@
+#
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+# This file was automatically generated by gh-aw. DO NOT EDIT.
+#
+# To update this file, edit the corresponding .md file and run:
+# gh aw compile
+# For more information: https://github.com/githubnext/gh-aw/blob/main/.github/aw/github-agentic-workflows.md
+#
+# Comprehensive smoke test for all project-related features
+#
+# frontmatter-hash: 33281c73d240f3c47970bbc9cd72f0bab4d7445cc164085af15ad139ca2c8fd0
+
+name: "Smoke Test - Projects"
+"on":
+ pull_request:
+ # names: # Label filtering applied via job conditions
+ # - smoke-projects # Label filtering applied via job conditions
+ types:
+ - labeled
+ schedule:
+ - cron: "34 */24 * * *"
+ # Friendly format: every 24h (scattered)
+ workflow_dispatch:
+
+permissions: {}
+
+concurrency:
+ group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
+ cancel-in-progress: true
+
+run-name: "Smoke Test - Projects"
+
+jobs:
+ activation:
+ needs: pre_activation
+ if: >
+ (needs.pre_activation.outputs.activated == 'true') && (((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id == github.repository_id)) &&
+ ((github.event_name != 'pull_request') || ((github.event.action != 'labeled') || (github.event.label.name == 'smoke-projects'))))
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ outputs:
+ comment_id: ""
+ comment_repo: ""
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Check workflow file timestamps
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_WORKFLOW_FILE: "smoke-project.lock.yml"
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ await main();
+
+ agent:
+ needs: activation
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ issues: read
+ pull-requests: read
+ env:
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ""
+ GH_AW_ASSETS_BRANCH: ""
+ GH_AW_ASSETS_MAX_SIZE_KB: 0
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ outputs:
+ has_patch: ${{ steps.collect_output.outputs.has_patch }}
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ output: ${{ steps.collect_output.outputs.output }}
+ output_types: ${{ steps.collect_output.outputs.output_types }}
+ secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Checkout repository
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
+ with:
+ persist-credentials: false
+ - name: Create gh-aw temp directory
+ run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Checkout PR branch
+ if: |
+ github.event.pull_request
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs');
+ await main();
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://githubnext.github.io/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Install GitHub Copilot CLI
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.400
+ - name: Install awf binary
+ run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.11.2
+ - name: Determine automatic lockdown mode for GitHub MCP server
+ id: determine-automatic-lockdown
+ env:
+ TOKEN_CHECK: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ if: env.TOKEN_CHECK != ''
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
+ await determineAutomaticLockdown(github, context, core);
+ - name: Download container images
+ run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/github-mcp-server:v0.30.2 ghcr.io/githubnext/gh-aw-mcpg:v0.0.86 node:lts-alpine
+ - name: Write Safe Outputs Config
+ run: |
+ mkdir -p /opt/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
+ cat > /opt/gh-aw/safeoutputs/config.json << 'EOF'
+ {"add_comment":{"max":1},"copy_project":{"max":2},"create_project":{"max":2,"target_owner":"github-agentic-workflows","title_prefix":"Smoke Test -"},"create_project_status_update":{"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1},"update_project":{"max":10}}
+ EOF
+ cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF'
+ [
+ {
+ "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 1 comment(s) can be added.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "body": {
+ "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation.",
+ "type": "string"
+ },
+ "item_number": {
+ "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool will attempt to resolve the target from the current workflow context (triggering issue, PR, or discussion).",
+ "type": "number"
+ }
+ },
+ "required": [
+ "body"
+ ],
+ "type": "object"
+ },
+ "name": "add_comment"
+ },
+ {
+ "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "alternatives": {
+ "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
+ "type": "string"
+ },
+ "reason": {
+ "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).",
+ "type": "string"
+ },
+ "tool": {
+ "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "reason"
+ ],
+ "type": "object"
+ },
+ "name": "missing_tool"
+ },
+ {
+ "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "message": {
+ "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').",
+ "type": "string"
+ }
+ },
+ "required": [
+ "message"
+ ],
+ "type": "object"
+ },
+ "name": "noop"
+ },
+ {
+ "description": "Add or update items in GitHub Projects v2 boards. Can add issues/PRs to a project and update custom field values. Requires the project URL, content type (issue or pull_request), and content number. Use campaign_id to group related items.\n\nThree usage modes:\n1. Add/update project item: Requires project + content_type. For 'issue' or 'pull_request', also requires content_number. For 'draft_issue', requires draft_title.\n2. Create project fields: Requires project + operation='create_fields' + field_definitions.\n3. Create project view: Requires project + operation='create_view' + view.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "campaign_id": {
+ "description": "Campaign identifier to group related project items. Used to track items created by the same campaign or workflow run.",
+ "type": "string"
+ },
+ "content_number": {
+ "description": "Issue or pull request number to add to the project. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123 for issue #123, or 456 in github.com/owner/repo/pull/456 for PR #456). Required when content_type is 'issue' or 'pull_request'.",
+ "type": "number"
+ },
+ "content_type": {
+ "description": "Type of item to add to the project. Use 'issue' or 'pull_request' to add existing repo content, or 'draft_issue' to create a draft item inside the project. Required when operation is not specified.",
+ "enum": [
+ "issue",
+ "pull_request",
+ "draft_issue"
+ ],
+ "type": "string"
+ },
+ "create_if_missing": {
+ "description": "Whether to create the project if it doesn't exist. Defaults to false. Requires projects:write permission when true.",
+ "type": "boolean"
+ },
+ "draft_body": {
+ "description": "Optional body for a Projects v2 draft issue (markdown). Only used when content_type is 'draft_issue'.",
+ "type": "string"
+ },
+ "draft_title": {
+ "description": "Title for a Projects v2 draft issue. Required when content_type is 'draft_issue'.",
+ "type": "string"
+ },
+ "field_definitions": {
+ "description": "Field definitions to create when operation is create_fields. Required when operation='create_fields'.",
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "data_type": {
+ "description": "Field type. Use SINGLE_SELECT with options for enumerated values.",
+ "enum": [
+ "TEXT",
+ "NUMBER",
+ "DATE",
+ "SINGLE_SELECT",
+ "ITERATION"
+ ],
+ "type": "string"
+ },
+ "name": {
+ "description": "Field name to create (e.g., 'size', 'priority').",
+ "type": "string"
+ },
+ "options": {
+ "description": "Options for SINGLE_SELECT fields.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "name",
+ "data_type"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "fields": {
+ "description": "Custom field values to set on the project item (e.g., {'Status': 'In Progress', 'Priority': 'High'}). Field names must match custom fields defined in the project.",
+ "type": "object"
+ },
+ "operation": {
+ "description": "Optional operation mode. Use create_fields to create required campaign fields up-front, or create_view to add a project view. When omitted, the tool adds/updates project items.",
+ "enum": [
+ "create_fields",
+ "create_view"
+ ],
+ "type": "string"
+ },
+ "project": {
+ "description": "Full GitHub project URL (e.g., 'https://github.com/orgs/myorg/projects/42' or 'https://github.com/users/username/projects/5'). Project names or numbers alone are NOT accepted.",
+ "pattern": "^https://github\\.com/(orgs|users)/[^/]+/projects/\\d+$",
+ "type": "string"
+ },
+ "view": {
+ "additionalProperties": false,
+ "description": "View definition to create when operation is create_view. Required when operation='create_view'.",
+ "properties": {
+ "filter": {
+ "type": "string"
+ },
+ "layout": {
+ "enum": [
+ "table",
+ "board",
+ "roadmap"
+ ],
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "visible_fields": {
+ "description": "Field IDs to show in the view (table/board only).",
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "name",
+ "layout"
+ ],
+ "type": "object"
+ }
+ },
+ "required": [
+ "project"
+ ],
+ "type": "object"
+ },
+ "name": "update_project"
+ },
+ {
+ "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "alternatives": {
+ "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
+ "type": "string"
+ },
+ "context": {
+ "description": "Additional context about the missing data or where it should come from (max 256 characters).",
+ "type": "string"
+ },
+ "data_type": {
+ "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.",
+ "type": "string"
+ },
+ "reason": {
+ "description": "Explanation of why this data is needed to complete the task (max 256 characters).",
+ "type": "string"
+ }
+ },
+ "required": [],
+ "type": "object"
+ },
+ "name": "missing_data"
+ },
+ {
+ "description": "Create a new empty GitHub Projects v2 board. Use this to create a project board for organizing work. The project is created empty and can be populated with issues and custom fields after creation.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "item_url": {
+ "description": "Optional GitHub issue URL to add as the first item to the project (e.g., 'https://github.com/owner/repo/issues/123').",
+ "pattern": "^https://github\\\\.com/[^/]+/[^/]+/issues/\\\\d+$",
+ "type": "string"
+ },
+ "owner": {
+ "description": "Login name of the organization or user that will own the new project (e.g., 'myorg' or 'username'). The token must have access to this owner. Optional if target-owner is configured in the workflow frontmatter.",
+ "type": "string"
+ },
+ "owner_type": {
+ "description": "Type of owner: 'org' for organization or 'user' for user account. Default: 'org'.",
+ "enum": [
+ "org",
+ "user"
+ ],
+ "type": "string"
+ },
+ "title": {
+ "description": "Title for the new project. Should be descriptive and unique within the owner's projects. If not provided, will be auto-generated using the title-prefix configuration (default: 'Campaign') as '\u003ctitle-prefix\u003e: \u003cissue-title\u003e' or '\u003ctitle-prefix\u003e #\u003cissue-number\u003e' based on the issue context.",
+ "type": "string"
+ }
+ },
+ "required": [],
+ "type": "object"
+ },
+ "name": "create_project"
+ },
+ {
+ "description": "Create a status update on a GitHub Projects v2 board to communicate project progress. Use this when you need to provide stakeholder updates with status indicators, timeline information, and progress summaries. Status updates create a historical record of project progress tracked over time. Requires project URL, status indicator, dates, and markdown body describing progress/trends/findings.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "body": {
+ "description": "Status update body in markdown format describing progress, findings, trends, and next steps. Should provide stakeholders with clear understanding of project state.",
+ "type": "string"
+ },
+ "project": {
+ "description": "Full GitHub project URL (e.g., 'https://github.com/orgs/myorg/projects/42' or 'https://github.com/users/username/projects/5'). Project names or numbers alone are NOT accepted.",
+ "pattern": "^https://github\\\\.com/(orgs|users)/[^/]+/projects/\\\\d+$",
+ "type": "string"
+ },
+ "start_date": {
+ "description": "Optional project start date in YYYY-MM-DD format (e.g., '2026-01-06').",
+ "pattern": "^\\\\d{4}-\\\\d{2}-\\\\d{2}$",
+ "type": "string"
+ },
+ "status": {
+ "description": "Status indicator for the project. Defaults to ON_TRACK. Values: ON_TRACK (progressing well), AT_RISK (has issues/blockers), OFF_TRACK (significantly behind), COMPLETE (finished), INACTIVE (paused/cancelled).",
+ "enum": [
+ "ON_TRACK",
+ "AT_RISK",
+ "OFF_TRACK",
+ "COMPLETE",
+ "INACTIVE"
+ ],
+ "type": "string"
+ },
+ "target_date": {
+ "description": "Optional project target/end date in YYYY-MM-DD format (e.g., '2026-12-31').",
+ "pattern": "^\\\\d{4}-\\\\d{2}-\\\\d{2}$",
+ "type": "string"
+ }
+ },
+ "required": [
+ "project",
+ "body"
+ ],
+ "type": "object"
+ },
+ "name": "create_project_status_update"
+ },
+ {
+ "description": "Copy a GitHub Projects v2 board to create a new project with the same structure, fields, and views. Useful for duplicating project templates or migrating projects between organizations. By default, draft issues are not copied unless includeDraftIssues is set to true. If the workflow has configured default values for source-project or target-owner, those fields become optional in the tool call.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "includeDraftIssues": {
+ "description": "Whether to copy draft issues from the source project. Default: false.",
+ "type": "boolean"
+ },
+ "owner": {
+ "description": "Login name of the organization or user that will own the new project (e.g., 'myorg' or 'username'). The token must have access to this owner. Optional if target-owner is configured in the workflow frontmatter.",
+ "type": "string"
+ },
+ "sourceProject": {
+ "description": "Full GitHub project URL of the source project to copy (e.g., 'https://github.com/orgs/myorg/projects/42' or 'https://github.com/users/username/projects/5'). Optional if source-project is configured in the workflow frontmatter.",
+ "pattern": "^https://github\\\\.com/(orgs|users)/[^/]+/projects/\\\\d+$",
+ "type": "string"
+ },
+ "title": {
+ "description": "Title for the new project. Should be descriptive and unique within the owner's projects.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "title"
+ ],
+ "type": "object"
+ },
+ "name": "copy_project"
+ }
+ ]
+ EOF
+ cat > /opt/gh-aw/safeoutputs/validation.json << 'EOF'
+ {
+ "add_comment": {
+ "defaultMax": 1,
+ "fields": {
+ "body": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "item_number": {
+ "issueOrPRNumber": true
+ }
+ }
+ },
+ "create_project": {
+ "defaultMax": 1,
+ "fields": {
+ "item_url": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512,
+ "pattern": "^https://github\\.com/[^/]+/[^/]+/issues/\\d+",
+ "patternError": "must be a full GitHub issue URL (e.g., https://github.com/owner/repo/issues/123)"
+ },
+ "owner": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "owner_type": {
+ "type": "string",
+ "enum": [
+ "org",
+ "user"
+ ]
+ },
+ "title": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ }
+ }
+ },
+ "create_project_status_update": {
+ "defaultMax": 10,
+ "fields": {
+ "body": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65536
+ },
+ "project": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512,
+ "pattern": "^https://github\\.com/(orgs|users)/[^/]+/projects/\\d+",
+ "patternError": "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)"
+ },
+ "start_date": {
+ "type": "string",
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
+ "patternError": "must be in YYYY-MM-DD format"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "INACTIVE",
+ "ON_TRACK",
+ "AT_RISK",
+ "OFF_TRACK",
+ "COMPLETE"
+ ]
+ },
+ "target_date": {
+ "type": "string",
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
+ "patternError": "must be in YYYY-MM-DD format"
+ }
+ }
+ },
+ "missing_tool": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512
+ },
+ "reason": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "tool": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "noop": {
+ "defaultMax": 1,
+ "fields": {
+ "message": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ }
+ }
+ },
+ "update_project": {
+ "defaultMax": 10,
+ "fields": {
+ "campaign_id": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "content_number": {
+ "optionalPositiveInteger": true
+ },
+ "content_type": {
+ "type": "string",
+ "enum": [
+ "issue",
+ "pull_request"
+ ]
+ },
+ "fields": {
+ "type": "object"
+ },
+ "issue": {
+ "optionalPositiveInteger": true
+ },
+ "project": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512,
+ "pattern": "^https://github\\.com/(orgs|users)/[^/]+/projects/\\d+",
+ "patternError": "must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/42)"
+ },
+ "pull_request": {
+ "optionalPositiveInteger": true
+ }
+ }
+ }
+ }
+ EOF
+ - name: Generate Safe Outputs MCP Server Config
+ id: safe-outputs-config
+ run: |
+ # Generate a secure random API key (360 bits of entropy, 40+ chars)
+ API_KEY=""
+ API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ PORT=3001
+
+ # Register API key as secret to mask it from logs
+ echo "::add-mask::${API_KEY}"
+
+ # Set outputs for next steps
+ {
+ echo "safe_outputs_api_key=${API_KEY}"
+ echo "safe_outputs_port=${PORT}"
+ } >> "$GITHUB_OUTPUT"
+
+ echo "Safe Outputs MCP server will run on port ${PORT}"
+
+ - name: Start Safe Outputs MCP HTTP Server
+ id: safe-outputs-start
+ env:
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ run: |
+ # Environment variables are set above to prevent template injection
+ export GH_AW_SAFE_OUTPUTS_PORT
+ export GH_AW_SAFE_OUTPUTS_API_KEY
+ export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
+ export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
+ export GH_AW_MCP_LOG_DIR
+
+ bash /opt/gh-aw/actions/start_safe_outputs_server.sh
+
+ - name: Start MCP gateway
+ id: start-mcp-gateway
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
+ GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ run: |
+ set -eo pipefail
+ mkdir -p /tmp/gh-aw/mcp-config
+
+ # Export gateway environment variables for MCP config and gateway script
+ export MCP_GATEWAY_PORT="80"
+ export MCP_GATEWAY_DOMAIN="host.docker.internal"
+ MCP_GATEWAY_API_KEY=""
+ MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ export MCP_GATEWAY_API_KEY
+
+ # Register API key as secret to mask it from logs
+ echo "::add-mask::${MCP_GATEWAY_API_KEY}"
+ export GH_AW_ENGINE="copilot"
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.86'
+
+ mkdir -p /home/runner/.copilot
+ cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
+ {
+ "mcpServers": {
+ "github": {
+ "type": "stdio",
+ "container": "ghcr.io/github/github-mcp-server:v0.30.2",
+ "env": {
+ "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN",
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
+ "GITHUB_READ_ONLY": "1",
+ "GITHUB_TOOLSETS": "context,repos,issues,pull_requests"
+ }
+ },
+ "safeoutputs": {
+ "type": "http",
+ "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
+ "headers": {
+ "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
+ }
+ }
+ },
+ "gateway": {
+ "port": $MCP_GATEWAY_PORT,
+ "domain": "${MCP_GATEWAY_DOMAIN}",
+ "apiKey": "${MCP_GATEWAY_API_KEY}"
+ }
+ }
+ MCPCONFIG_EOF
+ - name: Generate agentic run info
+ id: generate_aw_info
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ with:
+ script: |
+ const fs = require('fs');
+
+ const awInfo = {
+ engine_id: "copilot",
+ engine_name: "GitHub Copilot CLI",
+ model: process.env.GH_AW_MODEL_AGENT_COPILOT || "",
+ version: "",
+ agent_version: "0.0.400",
+ workflow_name: "Smoke Test - Projects",
+ experimental: false,
+ supports_tools_allowlist: true,
+ supports_http_transport: true,
+ run_id: context.runId,
+ run_number: context.runNumber,
+ run_attempt: process.env.GITHUB_RUN_ATTEMPT,
+ repository: context.repo.owner + '/' + context.repo.repo,
+ ref: context.ref,
+ sha: context.sha,
+ actor: context.actor,
+ event_name: context.eventName,
+ staged: false,
+ allowed_domains: ["defaults","github"],
+ firewall_enabled: true,
+ awf_version: "v0.11.2",
+ awmg_version: "v0.0.86",
+ steps: {
+ firewall: "squid"
+ },
+ created_at: new Date().toISOString()
+ };
+
+ // Write to /tmp/gh-aw directory to avoid inclusion in PR
+ const tmpPath = '/tmp/gh-aw/aw_info.json';
+ fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2));
+ console.log('Generated aw_info.json at:', tmpPath);
+ console.log(JSON.stringify(awInfo, null, 2));
+
+ // Set model as output for reuse in other steps/jobs
+ core.setOutput('model', awInfo.model);
+ - name: Generate workflow overview
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ with:
+ script: |
+ const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
+ await generateWorkflowOverview(core);
+ - name: Create prompt with built-in context
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ run: |
+ bash /opt/gh-aw/actions/create_prompt_first.sh
+ cat << 'PROMPT_EOF' > "$GH_AW_PROMPT"
+
+ PROMPT_EOF
+ cat "/opt/gh-aw/prompts/temp_folder_prompt.md" >> "$GH_AW_PROMPT"
+ cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT"
+ cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
+
+ GitHub API Access Instructions
+
+ The gh CLI is NOT authenticated. Do NOT use gh commands for GitHub operations.
+
+
+ To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls.
+
+ Discover available tools from the safeoutputs MCP server.
+
+ **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped.
+
+ **Note**: If you made no other safe output tool calls during this workflow execution, call the "noop" tool to provide a status message indicating completion or that no actions were needed.
+
+
+
+ The following GitHub context information is available for this workflow:
+ {{#if __GH_AW_GITHUB_ACTOR__ }}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_REPOSITORY__ }}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_WORKSPACE__ }}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
+ - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
+ - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
+ - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
+ - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_RUN_ID__ }}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ PROMPT_EOF
+ cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
+
+ PROMPT_EOF
+ cat << 'PROMPT_EOF' >> "$GH_AW_PROMPT"
+ {{#runtime-import workflows/smoke-project.md}}
+ PROMPT_EOF
+ - name: Substitute placeholders
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ with:
+ script: |
+ const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs');
+
+ // Call the substitution function
+ return await substitutePlaceholders({
+ file: process.env.GH_AW_PROMPT,
+ substitutions: {
+ GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
+ GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
+ GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
+ GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE
+ }
+ });
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs');
+ await main();
+ - name: Validate prompt placeholders
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
+ - name: Print prompt
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/print_prompt_summary.sh
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ timeout-minutes: 15
+ run: |
+ set -o pipefail
+ GH_AW_TOOL_BINS=""; command -v go >/dev/null 2>&1 && GH_AW_TOOL_BINS="$(go env GOROOT)/bin:$GH_AW_TOOL_BINS"; [ -n "$JAVA_HOME" ] && GH_AW_TOOL_BINS="$JAVA_HOME/bin:$GH_AW_TOOL_BINS"; [ -n "$CARGO_HOME" ] && GH_AW_TOOL_BINS="$CARGO_HOME/bin:$GH_AW_TOOL_BINS"; [ -n "$GEM_HOME" ] && GH_AW_TOOL_BINS="$GEM_HOME/bin:$GH_AW_TOOL_BINS"; [ -n "$CONDA" ] && GH_AW_TOOL_BINS="$CONDA/bin:$GH_AW_TOOL_BINS"; [ -n "$PIPX_BIN_DIR" ] && GH_AW_TOOL_BINS="$PIPX_BIN_DIR:$GH_AW_TOOL_BINS"; [ -n "$SWIFT_PATH" ] && GH_AW_TOOL_BINS="$SWIFT_PATH:$GH_AW_TOOL_BINS"; [ -n "$DOTNET_ROOT" ] && GH_AW_TOOL_BINS="$DOTNET_ROOT:$GH_AW_TOOL_BINS"; export GH_AW_TOOL_BINS
+ mkdir -p "$HOME/.cache"
+ sudo -E awf --env-all --env "ANDROID_HOME=${ANDROID_HOME}" --env "ANDROID_NDK=${ANDROID_NDK}" --env "ANDROID_NDK_HOME=${ANDROID_NDK_HOME}" --env "ANDROID_NDK_LATEST_HOME=${ANDROID_NDK_LATEST_HOME}" --env "ANDROID_NDK_ROOT=${ANDROID_NDK_ROOT}" --env "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" --env "AZURE_EXTENSION_DIR=${AZURE_EXTENSION_DIR}" --env "CARGO_HOME=${CARGO_HOME}" --env "CHROMEWEBDRIVER=${CHROMEWEBDRIVER}" --env "CONDA=${CONDA}" --env "DOTNET_ROOT=${DOTNET_ROOT}" --env "EDGEWEBDRIVER=${EDGEWEBDRIVER}" --env "GECKOWEBDRIVER=${GECKOWEBDRIVER}" --env "GEM_HOME=${GEM_HOME}" --env "GEM_PATH=${GEM_PATH}" --env "GOPATH=${GOPATH}" --env "GOROOT=${GOROOT}" --env "HOMEBREW_CELLAR=${HOMEBREW_CELLAR}" --env "HOMEBREW_PREFIX=${HOMEBREW_PREFIX}" --env "HOMEBREW_REPOSITORY=${HOMEBREW_REPOSITORY}" --env "JAVA_HOME=${JAVA_HOME}" --env "JAVA_HOME_11_X64=${JAVA_HOME_11_X64}" --env "JAVA_HOME_17_X64=${JAVA_HOME_17_X64}" --env "JAVA_HOME_21_X64=${JAVA_HOME_21_X64}" --env "JAVA_HOME_25_X64=${JAVA_HOME_25_X64}" --env "JAVA_HOME_8_X64=${JAVA_HOME_8_X64}" --env "NVM_DIR=${NVM_DIR}" --env "PIPX_BIN_DIR=${PIPX_BIN_DIR}" --env "PIPX_HOME=${PIPX_HOME}" --env "RUSTUP_HOME=${RUSTUP_HOME}" --env "SELENIUM_JAR_PATH=${SELENIUM_JAR_PATH}" --env "SWIFT_PATH=${SWIFT_PATH}" --env "VCPKG_INSTALLATION_ROOT=${VCPKG_INSTALLATION_ROOT}" --env "GH_AW_TOOL_BINS=$GH_AW_TOOL_BINS" --container-workdir "${GITHUB_WORKSPACE}" --mount /tmp:/tmp:rw --mount "${HOME}/.cache:${HOME}/.cache:rw" --mount "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}:rw" --mount /usr/bin/cat:/usr/bin/cat:ro --mount /usr/bin/curl:/usr/bin/curl:ro --mount /usr/bin/date:/usr/bin/date:ro --mount /usr/bin/find:/usr/bin/find:ro --mount /usr/bin/gh:/usr/bin/gh:ro --mount /usr/bin/grep:/usr/bin/grep:ro --mount /usr/bin/jq:/usr/bin/jq:ro --mount /usr/bin/yq:/usr/bin/yq:ro --mount /usr/bin/cp:/usr/bin/cp:ro --mount /usr/bin/cut:/usr/bin/cut:ro --mount /usr/bin/diff:/usr/bin/diff:ro --mount /usr/bin/head:/usr/bin/head:ro --mount /usr/bin/ls:/usr/bin/ls:ro --mount /usr/bin/mkdir:/usr/bin/mkdir:ro --mount /usr/bin/rm:/usr/bin/rm:ro --mount /usr/bin/sed:/usr/bin/sed:ro --mount /usr/bin/sort:/usr/bin/sort:ro --mount /usr/bin/tail:/usr/bin/tail:ro --mount /usr/bin/wc:/usr/bin/wc:ro --mount /usr/bin/which:/usr/bin/which:ro --mount /usr/local/bin/copilot:/usr/local/bin/copilot:ro --mount /home/runner/.copilot:/home/runner/.copilot:rw --mount /opt/hostedtoolcache:/opt/hostedtoolcache:ro --mount /opt/gh-aw:/opt/gh-aw:ro --allow-domains '*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.11.2 --agent-image act \
+ -- 'source /opt/gh-aw/actions/sanitize_path.sh "$GH_AW_TOOL_BINS$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH" && /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' \
+ 2>&1 | tee /tmp/gh-aw/agent-stdio.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
+ GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ XDG_CONFIG_HOME: /home/runner
+ - name: Copy Copilot session state files to logs
+ if: always()
+ continue-on-error: true
+ run: |
+ # Copy Copilot session state files to logs folder for artifact collection
+ # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them
+ SESSION_STATE_DIR="$HOME/.copilot/session-state"
+ LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs"
+
+ if [ -d "$SESSION_STATE_DIR" ]; then
+ echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR"
+ mkdir -p "$LOGS_DIR"
+ cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true
+ echo "Session state files copied successfully"
+ else
+ echo "No session-state directory found at $SESSION_STATE_DIR"
+ fi
+ - name: Stop MCP gateway
+ if: always()
+ continue-on-error: true
+ env:
+ MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
+ MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
+ run: |
+ bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
+ - name: Redact secrets in logs
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs');
+ await main();
+ env:
+ GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
+ SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Upload Safe Outputs
+ if: always()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: safe-output
+ path: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ if-no-files-found: warn
+ - name: Ingest agent output
+ id: collect_output
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs');
+ await main();
+ - name: Upload sanitized agent output
+ if: always() && env.GH_AW_AGENT_OUTPUT
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: agent-output
+ path: ${{ env.GH_AW_AGENT_OUTPUT }}
+ if-no-files-found: warn
+ - name: Upload engine output files
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: agent_outputs
+ path: |
+ /tmp/gh-aw/sandbox/agent/logs/
+ /tmp/gh-aw/redacted-urls.log
+ if-no-files-found: ignore
+ - name: Parse agent logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs');
+ await main();
+ - name: Parse MCP gateway logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ await main();
+ - name: Print firewall logs
+ if: always()
+ continue-on-error: true
+ env:
+ AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
+ run: |
+ # Fix permissions on firewall logs so they can be uploaded as artifacts
+ # AWF runs with sudo, creating files owned by root
+ sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
+ awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
+ - name: Upload agent artifacts
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: agent-artifacts
+ path: |
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/mcp-logs/
+ /tmp/gh-aw/sandbox/firewall/logs/
+ /tmp/gh-aw/agent-stdio.log
+ if-no-files-found: ignore
+
+ conclusion:
+ needs:
+ - activation
+ - agent
+ - detection
+ - safe_outputs
+ if: (always()) && (needs.agent.result != 'skipped')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ discussions: write
+ issues: write
+ pull-requests: write
+ outputs:
+ noop_message: ${{ steps.noop.outputs.noop_message }}
+ tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
+ total_count: ${{ steps.missing_tool.outputs.total_count }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Debug job inputs
+ env:
+ COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
+ COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
+ AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
+ AGENT_CONCLUSION: ${{ needs.agent.result }}
+ run: |
+ echo "Comment ID: $COMMENT_ID"
+ echo "Comment Repo: $COMMENT_REPO"
+ echo "Agent Output Types: $AGENT_OUTPUT_TYPES"
+ echo "Agent Conclusion: $AGENT_CONCLUSION"
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/safeoutputs/
+ - name: Setup agent output environment variable
+ run: |
+ mkdir -p /tmp/gh-aw/safeoutputs/
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process No-Op Messages
+ id: noop
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_NOOP_MAX: 1
+ GH_AW_WORKFLOW_NAME: "Smoke Test - Projects"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/noop.cjs');
+ await main();
+ - name: Record Missing Tool
+ id: missing_tool
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Smoke Test - Projects"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/missing_tool.cjs');
+ await main();
+ - name: Handle Agent Failure
+ id: handle_agent_failure
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Smoke Test - Projects"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }}
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔬 *Project features validation by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Testing all project features... [{workflow_name}]({run_url})\",\"runSuccess\":\"✅ All project tests passed! [{workflow_name}]({run_url})\",\"runFailure\":\"❌ Project test failed! [{workflow_name}]({run_url}): {status}\"}"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs');
+ await main();
+ - name: Update reaction comment with completion status
+ id: conclusion
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
+ GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_WORKFLOW_NAME: "Smoke Test - Projects"
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.result }}
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔬 *Project features validation by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Testing all project features... [{workflow_name}]({run_url})\",\"runSuccess\":\"✅ All project tests passed! [{workflow_name}]({run_url})\",\"runFailure\":\"❌ Project test failed! [{workflow_name}]({run_url}): {status}\"}"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/notify_comment_error.cjs');
+ await main();
+
+ detection:
+ needs: agent
+ if: needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true'
+ runs-on: ubuntu-latest
+ permissions: {}
+ timeout-minutes: 10
+ outputs:
+ success: ${{ steps.parse_results.outputs.success }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Download agent artifacts
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ with:
+ name: agent-artifacts
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/threat-detection/
+ - name: Echo agent output types
+ env:
+ AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
+ run: |
+ echo "Agent output-types: $AGENT_OUTPUT_TYPES"
+ - name: Setup threat detection
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ WORKFLOW_NAME: "Smoke Test - Projects"
+ WORKFLOW_DESCRIPTION: "Comprehensive smoke test for all project-related features"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs');
+ await main();
+ - name: Ensure threat-detection directory and log
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection
+ touch /tmp/gh-aw/threat-detection/detection.log
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://githubnext.github.io/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Install GitHub Copilot CLI
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.400
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ # --allow-tool shell(cat)
+ # --allow-tool shell(grep)
+ # --allow-tool shell(head)
+ # --allow-tool shell(jq)
+ # --allow-tool shell(ls)
+ # --allow-tool shell(tail)
+ # --allow-tool shell(wc)
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"
+ mkdir -p /tmp/
+ mkdir -p /tmp/gh-aw/
+ mkdir -p /tmp/gh-aw/agent/
+ mkdir -p /tmp/gh-aw/sandbox/agent/logs/
+ copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool 'shell(cat)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(jq)' --allow-tool 'shell(ls)' --allow-tool 'shell(tail)' --allow-tool 'shell(wc)' --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$COPILOT_CLI_INSTRUCTION"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"} 2>&1 | tee /tmp/gh-aw/threat-detection/detection.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ XDG_CONFIG_HOME: /home/runner
+ - name: Parse threat detection results
+ id: parse_results
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs');
+ await main();
+ - name: Upload threat detection log
+ if: always()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: threat-detection.log
+ path: /tmp/gh-aw/threat-detection/detection.log
+ if-no-files-found: ignore
+
+ pre_activation:
+ if: >
+ ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id == github.repository_id)) &&
+ ((github.event_name != 'pull_request') || ((github.event.action != 'labeled') || (github.event.label.name == 'smoke-projects')))
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ outputs:
+ activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Check team membership for workflow
+ id: check_membership
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_REQUIRED_ROLES: admin,maintainer,write
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/check_membership.cjs');
+ await main();
+
+ safe_outputs:
+ needs:
+ - agent
+ - detection
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.detection.outputs.success == 'true')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ discussions: write
+ issues: write
+ pull-requests: write
+ timeout-minutes: 15
+ env:
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔬 *Project features validation by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔬 Testing all project features... [{workflow_name}]({run_url})\",\"runSuccess\":\"✅ All project tests passed! [{workflow_name}]({run_url})\",\"runFailure\":\"❌ Project test failed! [{workflow_name}]({run_url}): {status}\"}"
+ GH_AW_WORKFLOW_ID: "smoke-project"
+ GH_AW_WORKFLOW_NAME: "Smoke Test - Projects"
+ outputs:
+ process_project_safe_outputs_processed_count: ${{ steps.process_project_safe_outputs.outputs.processed_count }}
+ process_project_safe_outputs_temporary_project_map: ${{ steps.process_project_safe_outputs.outputs.temporary_project_map }}
+ process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
+ steps:
+ - name: Checkout actions folder
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
+ with:
+ sparse-checkout: |
+ actions
+ persist-credentials: false
+ - name: Setup Scripts
+ uses: ./actions/setup
+ with:
+ destination: /opt/gh-aw/actions
+ safe-output-projects: 'true'
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/safeoutputs/
+ - name: Setup agent output environment variable
+ run: |
+ mkdir -p /tmp/gh-aw/safeoutputs/
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process Project-Related Safe Outputs
+ id: process_project_safe_outputs
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG: "{\"copy_project\":{\"max\":2,\"source_project\":\"https://github.com/orgs/github-agentic-workflows/projects/1\",\"target_owner\":\"github-agentic-workflows\"},\"create_project\":{\"max\":2,\"target_owner\":\"github-agentic-workflows\",\"title_prefix\":\"Smoke Test -\"},\"create_project_status_update\":{\"max\":5},\"update_project\":{\"max\":10}}"
+ GH_AW_PROJECT_GITHUB_TOKEN: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}
+ GH_AW_PROJECT_URL: "https://github.com/orgs/github-agentic-workflows/projects/1"
+ with:
+ github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/safe_output_project_handler_manager.cjs');
+ await main();
+ - name: Process Safe Outputs
+ id: process_safe_outputs
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_TEMPORARY_PROJECT_MAP: ${{ steps.process_project_safe_outputs.outputs.temporary_project_map }}
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"missing_data\":{},\"missing_tool\":{}}"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs');
+ await main();
+
diff --git a/.github/workflows/smoke-project.md b/.github/workflows/smoke-project.md
new file mode 100644
index 0000000000..2aa00d65c8
--- /dev/null
+++ b/.github/workflows/smoke-project.md
@@ -0,0 +1,369 @@
+---
+description: Comprehensive smoke test for all project-related features
+on:
+ workflow_dispatch:
+ schedule: every 24h
+ pull_request:
+ types: [labeled]
+ names: ["smoke-projects"]
+permissions:
+ contents: read
+ pull-requests: read
+ issues: read
+name: Smoke Test - Projects
+engine: copilot
+strict: true
+project: "https://github.com/orgs/github-agentic-workflows/projects/1"
+network:
+ allowed:
+ - defaults
+ - github
+tools:
+ bash:
+ - "*"
+ github:
+safe-outputs:
+ update-project:
+ max: 10
+ project: "https://github.com/orgs/github-agentic-workflows/projects/1"
+ create-project:
+ max: 2
+ target-owner: "github-agentic-workflows"
+ title-prefix: "Smoke Test -"
+ copy-project:
+ max: 2
+ source-project: "https://github.com/orgs/github-agentic-workflows/projects/1"
+ target-owner: "github-agentic-workflows"
+ create-project-status-update:
+ max: 5
+ project: "https://github.com/orgs/github-agentic-workflows/projects/1"
+ add-comment:
+ max: 1
+ messages:
+ footer: "> 🔬 *Project features validation by [{workflow_name}]({run_url})*"
+ run-started: "🔬 Testing all project features... [{workflow_name}]({run_url})"
+ run-success: "✅ All project tests passed! [{workflow_name}]({run_url})"
+ run-failure: "❌ Project test failed! [{workflow_name}]({run_url}): {status}"
+timeout-minutes: 15
+---
+
+# Smoke Test: Project Features
+
+**Purpose:** Comprehensive validation of all project-related features including frontmatter configuration and all project safe outputs.
+
+**Test Board:** https://github.com/orgs/github-agentic-workflows/projects/1
+
+**IMPORTANT: Keep outputs concise. Report each test with ✅ or ❌ status.**
+
+## Test Suite Overview
+
+This smoke test validates:
+1. **Project Frontmatter** - Automatic safe-outputs configuration from top-level `project:` field
+2. **Update Project** - All update-project operations (add, update, archive, views)
+3. **Create Project** - Creating new projects with custom views and fields
+4. **Copy Project** - Duplicating existing projects
+5. **Project Status Updates** - Creating status updates with all status types
+
+---
+
+## Section 1: Project Top-Level Frontmatter
+
+**Purpose:** Validate that the `project:` top-level frontmatter field automatically configures safe outputs.
+
+### Test 1.1: Verify Environment Variables
+Check that the `GH_AW_PROJECT_URL` environment variable is set:
+
+```bash
+echo "Project URL from env: $GH_AW_PROJECT_URL"
+```
+
+Expected: `https://github.com/orgs/github-agentic-workflows/projects/1`
+
+### Test 1.2: Verify Safe Output Configuration
+The frontmatter declares the `project:` field. Verify that the compiler automatically created the necessary safe output configurations with defaults:
+- `update-project` with max: 100
+- `create-project-status-update` with max: 1
+
+### Test 1.3: Test Update Project with Default URL
+Create a test draft issue using the default project URL from frontmatter:
+
+```json
+{
+ "type": "update_project",
+ "content_type": "draft_issue",
+ "draft_title": "Smoke Test - Frontmatter - Run ${{ github.run_id }}",
+ "fields": {
+ "status": "Todo"
+ }
+}
+```
+
+### Test 1.4: Test Project Status Update with Default URL
+Create a status update using the default project URL:
+
+```json
+{
+ "type": "create_project_status_update",
+ "body": "Smoke test validation - frontmatter project URL working correctly (Run: ${{ github.run_id }})",
+ "status": "ON_TRACK"
+}
+```
+
+---
+
+## Section 2: Update-Project Safe Output
+
+**Purpose:** Validate all operations of the `update-project` safe output.
+
+### Test 2.1: Add Draft Issue
+Create a new draft issue in the project:
+
+```json
+{
+ "type": "update_project",
+ "operation": "add_draft_issue",
+ "project": "https://github.com/orgs/github-agentic-workflows/projects/1",
+ "draft_title": "Smoke Test Draft - Run ${{ github.run_id }}",
+ "draft_body": "This is a test draft issue created by the update-project smoke test.",
+ "fields": {
+ "status": "Todo"
+ }
+}
+```
+
+### Test 2.2: Update Item Fields
+Update the draft issue from Test 2.1 (track the item ID):
+
+```json
+{
+ "type": "update_project",
+ "operation": "update_item",
+ "project": "https://github.com/orgs/github-agentic-workflows/projects/1",
+ "item_id": "",
+ "fields": {
+ "status": "In Progress"
+ }
+}
+```
+
+### Test 2.3: Archive Item
+Archive the draft issue created in Test 2.1:
+
+```json
+{
+ "type": "update_project",
+ "operation": "archive_item",
+ "project": "https://github.com/orgs/github-agentic-workflows/projects/1",
+ "item_id": ""
+}
+```
+
+---
+
+## Section 3: Create-Project Safe Output
+
+**Purpose:** Validate creating new GitHub Projects V2.
+
+### Test 3.1: Create Basic Project
+Create a simple project with minimal configuration:
+
+```json
+{
+ "type": "create_project",
+ "title": "Basic Smoke Test Project",
+ "owner": "github-agentic-workflows",
+ "description": "Test project created by smoke test - Run ${{ github.run_id }}"
+}
+```
+
+### Test 3.2: Create Project with Custom Views
+Create a project with multiple views:
+
+```json
+{
+ "type": "create_project",
+ "title": "Multi-View Test Project",
+ "owner": "github-agentic-workflows",
+ "description": "Test project with custom views - Run ${{ github.run_id }}",
+ "views": [
+ {
+ "name": "Backlog",
+ "layout": "TABLE"
+ },
+ {
+ "name": "Current Sprint",
+ "layout": "BOARD"
+ }
+ ]
+}
+```
+
+### Test 3.3: Verify Max Limit
+The frontmatter configures `max: 2` for create-project. After creating 2 projects, verify that a 3rd attempt is blocked by the max limit.
+
+---
+
+## Section 4: Copy-Project Safe Output
+
+**Purpose:** Validate duplicating existing projects.
+
+### Test 4.1: Basic Project Copy
+Copy the source project without draft issues:
+
+```json
+{
+ "type": "copy_project",
+ "source_project": "https://github.com/orgs/github-agentic-workflows/projects/1",
+ "target_owner": "github-agentic-workflows",
+ "new_title": "Smoke Test Copy 1 - Run ${{ github.run_id }}",
+ "include_draft_issues": false
+}
+```
+
+### Test 4.2: Copy with Draft Issues
+Copy the source project including draft issues:
+
+```json
+{
+ "type": "copy_project",
+ "source_project": "https://github.com/orgs/github-agentic-workflows/projects/1",
+ "target_owner": "github-agentic-workflows",
+ "new_title": "Smoke Test Copy 2 - Run ${{ github.run_id }}",
+ "include_draft_issues": true
+}
+```
+
+### Test 4.3: Verify Max Limit
+The frontmatter configures `max: 2` for copy-project. Verify that a 3rd copy attempt is blocked by the max limit.
+
+---
+
+## Section 5: Create-Project-Status-Update Safe Output
+
+**Purpose:** Validate creating project status updates.
+
+### Test 5.1: ON_TRACK Status Update
+Create a status update with ON_TRACK status:
+
+```json
+{
+ "type": "create_project_status_update",
+ "project": "https://github.com/orgs/github-agentic-workflows/projects/1",
+ "status": "ON_TRACK",
+ "body": "✅ Smoke test validation in progress - Run ${{ github.run_id }}. All systems nominal."
+}
+```
+
+### Test 5.2: AT_RISK Status Update
+Create a status update with AT_RISK status:
+
+```json
+{
+ "type": "create_project_status_update",
+ "project": "https://github.com/orgs/github-agentic-workflows/projects/1",
+ "status": "AT_RISK",
+ "body": "⚠️ Testing AT_RISK status - Run ${{ github.run_id }}. This is a simulated at-risk condition for testing purposes."
+}
+```
+
+### Test 5.3: OFF_TRACK Status Update
+Create a status update with OFF_TRACK status:
+
+```json
+{
+ "type": "create_project_status_update",
+ "project": "https://github.com/orgs/github-agentic-workflows/projects/1",
+ "status": "OFF_TRACK",
+ "body": "🔴 Testing OFF_TRACK status - Run ${{ github.run_id }}. This is a simulated off-track condition for testing purposes."
+}
+```
+
+### Test 5.4: Status Update with Markdown
+Create a status update with rich markdown formatting:
+
+```json
+{
+ "type": "create_project_status_update",
+ "project": "https://github.com/orgs/github-agentic-workflows/projects/1",
+ "status": "ON_TRACK",
+ "body": "## Smoke Test Progress Report\n\n**Run ID:** ${{ github.run_id }}\n\n### Completed:\n- ✅ Frontmatter tests\n- ✅ Update-project tests\n- ✅ Create-project tests\n- ✅ Copy-project tests\n\n### In Progress:\n- 🔄 Status update tests\n\n> 💡 **Note:** This is an automated smoke test validation."
+}
+```
+
+### Test 5.5: Verify Default Project URL
+Create a status update without specifying the project (should use frontmatter default):
+
+```json
+{
+ "type": "create_project_status_update",
+ "status": "ON_TRACK",
+ "body": "Testing default project URL from frontmatter - Run ${{ github.run_id }}"
+}
+```
+
+---
+
+## Output Requirements
+
+Add a **concise comment** to the pull request (if triggered by PR) with test results:
+
+### Section 1: Project Frontmatter
+| Test | Status | Notes |
+|------|--------|-------|
+| Environment variable set | ✅/❌ | GH_AW_PROJECT_URL |
+| Auto-configured update-project | ✅/❌ | Max: 100 |
+| Auto-configured status updates | ✅/❌ | Max: 1 |
+| Update project with default URL | ✅/❌ | Draft created |
+| Status update with default URL | ✅/❌ | Status posted |
+
+### Section 2: Update-Project
+| Operation | Status | Notes |
+|-----------|--------|-------|
+| Add draft issue | ✅/❌ | Item ID created |
+| Update item fields | ✅/❌ | Status changed |
+| Archive item | ✅/❌ | Item archived |
+
+### Section 3: Create-Project
+| Test Case | Status | Project URL |
+|-----------|--------|-------------|
+| Basic project | ✅/❌ | [URL] |
+| Multi-view project | ✅/❌ | [URL] |
+| Max limit enforced | ✅/❌ | 3rd blocked |
+
+### Section 4: Copy-Project
+| Test Case | Status | Project URL |
+|-----------|--------|-------------|
+| Copy without items | ✅/❌ | [URL] |
+| Copy with items | ✅/❌ | [URL] |
+| Max limit enforced | ✅/❌ | 3rd blocked |
+
+### Section 5: Status Updates
+| Test Case | Status | Notes |
+|-----------|--------|-------|
+| ON_TRACK status | ✅/❌ | Posted |
+| AT_RISK status | ✅/❌ | Posted |
+| OFF_TRACK status | ✅/❌ | Posted |
+| Markdown formatting | ✅/❌ | Rendered |
+| Default project URL | ✅/❌ | Used default |
+
+**Overall Status:** PASS / FAIL
+
+---
+
+## Success Criteria
+
+- All frontmatter tests pass (environment variables, auto-configuration)
+- All update-project operations succeed
+- Projects are created with custom views
+- Projects are copied correctly with/without items
+- All status update types work (ON_TRACK, AT_RISK, OFF_TRACK)
+- Markdown formatting renders properly in status updates
+- Max limits are enforced for all safe outputs
+- No errors in workflow logs
+
+## Cleanup
+
+After the test completes:
+1. Manually archive or delete the created test projects to avoid clutter
+2. Archive draft issues created during testing
+3. Verify all test artifacts are cleaned up from the test board