From 520e97eb7f2daf233ed97d157cc23e352b764eef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 03:26:48 +0000 Subject: [PATCH 1/9] Initial plan From a14507a137a22568aeb00fb81652e0334f2a42b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 03:31:15 +0000 Subject: [PATCH 2/9] chore: plan max-turn-cache-misses validation fix Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-copilot-aoai-apikey.lock.yml | 2 +- .github/workflows/smoke-copilot-aoai-entra.lock.yml | 2 +- .github/workflows/smoke-copilot.lock.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml index 75d8ec3be14..34570f0c263 100644 --- a/.github/workflows/smoke-copilot-aoai-apikey.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-apikey.lock.yml @@ -370,7 +370,7 @@ jobs: GH_AW_EXPERIMENT_SPEC: '{"caveman":{"variants":["yes","no"]},"subagent_model":{"variants":["small","large"]}}' GH_AW_EXPERIMENT_STATE_FILE: /tmp/gh-aw/experiments/state.jsonl GH_AW_EXPERIMENT_STATE_DIR: /tmp/gh-aw/experiments - GH_AW_HARNESS_VERSION: ade8fce6f67f827960135bbb036e795c01cc11c1101bd2aa2d99643e75746e89:2cf418e805934bf61cb85464b4d70e738e7912fa331083ee09d1e35754201e31 + GH_AW_HARNESS_VERSION: 29a1aa26ca657e2810c94d35b5e01af95368a25a52d53cf5020d77cc93539fae:b6a99112f7584dd539ecf8000668ffcd2ae1569e074e15266362f5d3c366790b with: script: | const path = require('path'); diff --git a/.github/workflows/smoke-copilot-aoai-entra.lock.yml b/.github/workflows/smoke-copilot-aoai-entra.lock.yml index a5a19113101..6b946ad99ac 100644 --- a/.github/workflows/smoke-copilot-aoai-entra.lock.yml +++ b/.github/workflows/smoke-copilot-aoai-entra.lock.yml @@ -374,7 +374,7 @@ jobs: GH_AW_EXPERIMENT_SPEC: '{"caveman":{"variants":["yes","no"]},"subagent_model":{"variants":["small","large"]}}' GH_AW_EXPERIMENT_STATE_FILE: /tmp/gh-aw/experiments/state.jsonl GH_AW_EXPERIMENT_STATE_DIR: /tmp/gh-aw/experiments - GH_AW_HARNESS_VERSION: b420c72edd7e4e8a248b54d4ac794d8ac242e55007250d798c2a488ff98e6066:af518be4a68619adfac95f3320b6b189753ce2329530682b54d2ea40abd80fe5 + GH_AW_HARNESS_VERSION: 7cf6bc36dc5c44e5ff350fb3757713d9a01866ddabc63f0949a61edc21c10ef1:82e14f6429d21faf3948e53f0ca2f5afe0985627b5dd8c0b751b5dd7e3502bc3 with: script: | const path = require('path'); diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 4ec37bf9164..691981d3541 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -376,7 +376,7 @@ jobs: GH_AW_EXPERIMENT_SPEC: '{"caveman":{"variants":["yes","no"]},"subagent_model":{"variants":["small","large"]}}' GH_AW_EXPERIMENT_STATE_FILE: /tmp/gh-aw/experiments/state.jsonl GH_AW_EXPERIMENT_STATE_DIR: /tmp/gh-aw/experiments - GH_AW_HARNESS_VERSION: 2d55e4f6b6e87ea18ca4d5ae3649ab9b63c81811f64e21b587e5db34ab024332:7e2a00590820be8c0fb1d74191ce56067cf48289f460f6fb867000ca2a000988 + GH_AW_HARNESS_VERSION: dafcc89aaee45059087e98ad722bf71b4c9ee1da5e15b83b0666cd260362c9aa:d898822925fb95367583fe71f057b05989a03914d233576bcf26c4ce897fad5e with: script: | const path = require('path'); From 0f37a58e2fb81414ea58416460d39849d3dfb8b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 03:35:13 +0000 Subject: [PATCH 3/9] fix: treat max-turn-cache-misses expressions as invalid input Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/engine_config_parser.go | 22 ++++++++++++++-------- pkg/workflow/engine_config_parser_test.go | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pkg/workflow/engine_config_parser.go b/pkg/workflow/engine_config_parser.go index 2544a782262..ae61fbbf3f0 100644 --- a/pkg/workflow/engine_config_parser.go +++ b/pkg/workflow/engine_config_parser.go @@ -37,19 +37,25 @@ func parseMaxTurnCacheMissesValue(raw any) int { } // parsePositiveIntValue parses a strictly-positive integer from raw. -// Delegates to parseIntOrExpressionValue for a single int-validation path. -// GitHub Actions expression strings (e.g. "${{ inputs.value }}") are silently -// treated as 0 (not configured) because these fields are integer-only. +// GitHub Actions expression strings (e.g. "${{ inputs.value }}") are treated +// as invalid for integer-only fields. func parsePositiveIntValue(raw any, fieldName string) int { - s := parseIntOrExpressionValue(raw, 1, fieldName) - if s == "" || isExpression(s) { + if val, ok := typeutil.ParseIntValue(raw); ok && val >= 1 { + return val + } + rawStr, ok := raw.(string) + if !ok { return 0 } - val, err := strconv.Atoi(s) - if err != nil { + trimmed := strings.TrimSpace(rawStr) + if trimmed == "" { return 0 } - return val + if parsed, err := strconv.Atoi(trimmed); err == nil && parsed >= 1 { + return parsed + } + engineLog.Printf("Ignoring invalid %s value: %q", fieldName, rawStr) + return 0 } func parseMaxTurnsValue(raw any) string { diff --git a/pkg/workflow/engine_config_parser_test.go b/pkg/workflow/engine_config_parser_test.go index a1223ae6f11..0d2940e5276 100644 --- a/pkg/workflow/engine_config_parser_test.go +++ b/pkg/workflow/engine_config_parser_test.go @@ -22,6 +22,7 @@ func TestParsePositiveIntValues(t *testing.T) { {name: "max-turn-cache-misses int", parse: parseMaxTurnCacheMissesValue, raw: 2, expected: 2}, {name: "max-turn-cache-misses string", parse: parseMaxTurnCacheMissesValue, raw: "7", expected: 7}, {name: "max-turn-cache-misses invalid", parse: parseMaxTurnCacheMissesValue, raw: "bad", expected: 0}, + {name: "max-turn-cache-misses expression invalid", parse: parseMaxTurnCacheMissesValue, raw: "${{ inputs.max_turn_cache_misses }}", expected: 0}, } for _, tt := range tests { From 34f7fcfc4d1e12177ec6f0c75785613322721ca5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 05:21:51 +0000 Subject: [PATCH 4/9] fix: reject invalid imported cache miss limits Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/workflow/compiler_orchestrator_engine.go | 17 +++++++++++ .../compiler_orchestrator_engine_test.go | 30 +++++++++++++++++++ pkg/workflow/engine_config_parser.go | 23 +++++++------- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/pkg/workflow/compiler_orchestrator_engine.go b/pkg/workflow/compiler_orchestrator_engine.go index bede98ad489..bb359886c7c 100644 --- a/pkg/workflow/compiler_orchestrator_engine.go +++ b/pkg/workflow/compiler_orchestrator_engine.go @@ -36,6 +36,9 @@ type engineSetupResult struct { // - Strict mode validations func (c *Compiler) setupEngineAndImports(result *parser.FrontmatterResult, cleanPath string, content []byte, markdownDir string) (*engineSetupResult, error) { orchestratorEngineLog.Printf("Setting up engine and processing imports") + if err := validateMaxTurnCacheMissesValue(result.Frontmatter["max-turn-cache-misses"]); err != nil { + return nil, err + } engineSetting, engineConfig, model := c.ExtractEngineConfig(result.Frontmatter) preservedMaxTurns, preservedMaxAICredits, preservedMaxRuns, preservedMaxTurnCacheMisses := extractEngineBudgetLimits(engineConfig) if err := c.validateAndRegisterInlineEngineConfig(engineConfig); err != nil { @@ -66,6 +69,9 @@ func (c *Compiler) setupEngineAndImports(result *parser.FrontmatterResult, clean if err != nil { return nil, err } + if err := validateImportedMaxTurnCacheMisses(importsResult.MergedMaxTurnCacheMisses); err != nil { + return nil, err + } sandboxConfig = mergeImportedSandboxAgentMounts(sandboxConfig, importsResult.MergedSandboxAgentMounts) sandboxConfig = mergeImportedSandboxAgentRuntimeInstall(sandboxConfig, importsResult.MergedSandboxAgentRuntimeInstall) engineSetting, engineConfig, model, importedEngineDefinitions, err := c.resolveEngineFromIncludesAndImports(result, markdownDir, importsResult, engineSetting, engineConfig, model) @@ -129,6 +135,17 @@ func extractEngineBudgetLimits(engineConfig *EngineConfig) (string, int64, int, return engineConfig.MaxTurns, engineConfig.MaxAICredits, engineConfig.MaxRuns, engineConfig.MaxTurnCacheMisses } +func validateImportedMaxTurnCacheMisses(raw string) error { + if raw == "" { + return nil + } + var value any + if err := json.Unmarshal([]byte(raw), &value); err != nil { + return errors.New("max-turn-cache-misses must be a positive integer. Example: max-turn-cache-misses: 5") + } + return validateMaxTurnCacheMissesValue(value) +} + func defaultNetworkPermissions(networkPermissions *NetworkPermissions) *NetworkPermissions { if networkPermissions != nil { return networkPermissions diff --git a/pkg/workflow/compiler_orchestrator_engine_test.go b/pkg/workflow/compiler_orchestrator_engine_test.go index 873ed3d6fe0..caeb773c610 100644 --- a/pkg/workflow/compiler_orchestrator_engine_test.go +++ b/pkg/workflow/compiler_orchestrator_engine_test.go @@ -299,6 +299,36 @@ imports: assert.Equal(t, 7, result.engineConfig.MaxTurnCacheMisses) } +func TestCompileWorkflowRejectsImportedMaxTurnCacheMissesExpression(t *testing.T) { + tmpDir := testutil.TempDir(t, "engine-imported-invalid-max-turn-cache-misses") + + sharedDir := filepath.Join(tmpDir, "shared") + require.NoError(t, os.MkdirAll(sharedDir, 0755)) + require.NoError(t, os.WriteFile(filepath.Join(sharedDir, "common.md"), []byte(`--- +max-turn-cache-misses: "${{ inputs.max_turn_cache_misses }}" +--- + +# Shared Workflow +`), 0644)) + + testFile := filepath.Join(tmpDir, "test.md") + require.NoError(t, os.WriteFile(testFile, []byte(`--- +on: push +engine: copilot +imports: + - shared/common.md +--- + +# Test Workflow +`), 0644)) + + err := NewCompiler().CompileWorkflow(testFile) + require.Error(t, err) + require.ErrorContains(t, err, "max-turn-cache-misses") + require.ErrorContains(t, err, "positive integer") + require.ErrorContains(t, err, "Example: max-turn-cache-misses: 5") +} + // TestSetupEngineAndImports_ImportedEngineVersionDefault verifies that a shared/imported // engine definition's top-level `version` field is applied as the default // EngineConfig.Version when the workflow's own `engine:` frontmatter selects the same diff --git a/pkg/workflow/engine_config_parser.go b/pkg/workflow/engine_config_parser.go index ae61fbbf3f0..019e24c10c3 100644 --- a/pkg/workflow/engine_config_parser.go +++ b/pkg/workflow/engine_config_parser.go @@ -1,6 +1,7 @@ package workflow import ( + "errors" "strconv" "strings" @@ -40,22 +41,22 @@ func parseMaxTurnCacheMissesValue(raw any) int { // GitHub Actions expression strings (e.g. "${{ inputs.value }}") are treated // as invalid for integer-only fields. func parsePositiveIntValue(raw any, fieldName string) int { - if val, ok := typeutil.ParseIntValue(raw); ok && val >= 1 { - return val - } - rawStr, ok := raw.(string) - if !ok { + value := parseIntOrExpressionValue(raw, 1, fieldName) + if value == "" || isExpression(value) { return 0 } - trimmed := strings.TrimSpace(rawStr) - if trimmed == "" { + parsed, err := strconv.Atoi(value) + if err != nil { return 0 } - if parsed, err := strconv.Atoi(trimmed); err == nil && parsed >= 1 { - return parsed + return parsed +} + +func validateMaxTurnCacheMissesValue(raw any) error { + if raw == nil || parseMaxTurnCacheMissesValue(raw) > 0 { + return nil } - engineLog.Printf("Ignoring invalid %s value: %q", fieldName, rawStr) - return 0 + return errors.New("max-turn-cache-misses must be a positive integer. Example: max-turn-cache-misses: 5") } func parseMaxTurnsValue(raw any) string { From eb53a7af76344bf9c3a0972b123e2ec0fd7cceed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:31:20 +0000 Subject: [PATCH 5/9] Allow max turn cache miss expressions Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/compiler_orchestrator_engine.go | 17 --------------- .../compiler_orchestrator_engine_test.go | 10 +++------ pkg/workflow/engine_config_parser.go | 21 +++++++------------ 3 files changed, 10 insertions(+), 38 deletions(-) diff --git a/pkg/workflow/compiler_orchestrator_engine.go b/pkg/workflow/compiler_orchestrator_engine.go index bb359886c7c..bede98ad489 100644 --- a/pkg/workflow/compiler_orchestrator_engine.go +++ b/pkg/workflow/compiler_orchestrator_engine.go @@ -36,9 +36,6 @@ type engineSetupResult struct { // - Strict mode validations func (c *Compiler) setupEngineAndImports(result *parser.FrontmatterResult, cleanPath string, content []byte, markdownDir string) (*engineSetupResult, error) { orchestratorEngineLog.Printf("Setting up engine and processing imports") - if err := validateMaxTurnCacheMissesValue(result.Frontmatter["max-turn-cache-misses"]); err != nil { - return nil, err - } engineSetting, engineConfig, model := c.ExtractEngineConfig(result.Frontmatter) preservedMaxTurns, preservedMaxAICredits, preservedMaxRuns, preservedMaxTurnCacheMisses := extractEngineBudgetLimits(engineConfig) if err := c.validateAndRegisterInlineEngineConfig(engineConfig); err != nil { @@ -69,9 +66,6 @@ func (c *Compiler) setupEngineAndImports(result *parser.FrontmatterResult, clean if err != nil { return nil, err } - if err := validateImportedMaxTurnCacheMisses(importsResult.MergedMaxTurnCacheMisses); err != nil { - return nil, err - } sandboxConfig = mergeImportedSandboxAgentMounts(sandboxConfig, importsResult.MergedSandboxAgentMounts) sandboxConfig = mergeImportedSandboxAgentRuntimeInstall(sandboxConfig, importsResult.MergedSandboxAgentRuntimeInstall) engineSetting, engineConfig, model, importedEngineDefinitions, err := c.resolveEngineFromIncludesAndImports(result, markdownDir, importsResult, engineSetting, engineConfig, model) @@ -135,17 +129,6 @@ func extractEngineBudgetLimits(engineConfig *EngineConfig) (string, int64, int, return engineConfig.MaxTurns, engineConfig.MaxAICredits, engineConfig.MaxRuns, engineConfig.MaxTurnCacheMisses } -func validateImportedMaxTurnCacheMisses(raw string) error { - if raw == "" { - return nil - } - var value any - if err := json.Unmarshal([]byte(raw), &value); err != nil { - return errors.New("max-turn-cache-misses must be a positive integer. Example: max-turn-cache-misses: 5") - } - return validateMaxTurnCacheMissesValue(value) -} - func defaultNetworkPermissions(networkPermissions *NetworkPermissions) *NetworkPermissions { if networkPermissions != nil { return networkPermissions diff --git a/pkg/workflow/compiler_orchestrator_engine_test.go b/pkg/workflow/compiler_orchestrator_engine_test.go index caeb773c610..837808a1117 100644 --- a/pkg/workflow/compiler_orchestrator_engine_test.go +++ b/pkg/workflow/compiler_orchestrator_engine_test.go @@ -299,8 +299,8 @@ imports: assert.Equal(t, 7, result.engineConfig.MaxTurnCacheMisses) } -func TestCompileWorkflowRejectsImportedMaxTurnCacheMissesExpression(t *testing.T) { - tmpDir := testutil.TempDir(t, "engine-imported-invalid-max-turn-cache-misses") +func TestCompileWorkflowAllowsImportedMaxTurnCacheMissesExpression(t *testing.T) { + tmpDir := testutil.TempDir(t, "engine-imported-max-turn-cache-misses-expression") sharedDir := filepath.Join(tmpDir, "shared") require.NoError(t, os.MkdirAll(sharedDir, 0755)) @@ -322,11 +322,7 @@ imports: # Test Workflow `), 0644)) - err := NewCompiler().CompileWorkflow(testFile) - require.Error(t, err) - require.ErrorContains(t, err, "max-turn-cache-misses") - require.ErrorContains(t, err, "positive integer") - require.ErrorContains(t, err, "Example: max-turn-cache-misses: 5") + require.NoError(t, NewCompiler().CompileWorkflow(testFile)) } // TestSetupEngineAndImports_ImportedEngineVersionDefault verifies that a shared/imported diff --git a/pkg/workflow/engine_config_parser.go b/pkg/workflow/engine_config_parser.go index 019e24c10c3..4ba9655665c 100644 --- a/pkg/workflow/engine_config_parser.go +++ b/pkg/workflow/engine_config_parser.go @@ -1,7 +1,6 @@ package workflow import ( - "errors" "strconv" "strings" @@ -38,25 +37,19 @@ func parseMaxTurnCacheMissesValue(raw any) int { } // parsePositiveIntValue parses a strictly-positive integer from raw. -// GitHub Actions expression strings (e.g. "${{ inputs.value }}") are treated -// as invalid for integer-only fields. +// Delegates to parseIntOrExpressionValue for a single int-validation path. +// GitHub Actions expression strings (e.g. "${{ inputs.value }}") are accepted +// but cannot be resolved to an integer at compile time, so they return 0. func parsePositiveIntValue(raw any, fieldName string) int { - value := parseIntOrExpressionValue(raw, 1, fieldName) - if value == "" || isExpression(value) { + s := parseIntOrExpressionValue(raw, 1, fieldName) + if s == "" || isExpression(s) { return 0 } - parsed, err := strconv.Atoi(value) + val, err := strconv.Atoi(s) if err != nil { return 0 } - return parsed -} - -func validateMaxTurnCacheMissesValue(raw any) error { - if raw == nil || parseMaxTurnCacheMissesValue(raw) > 0 { - return nil - } - return errors.New("max-turn-cache-misses must be a positive integer. Example: max-turn-cache-misses: 5") + return val } func parseMaxTurnsValue(raw any) string { From 90c9ef146710e9984c219591a59a522f0e4c430a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:41:52 +0000 Subject: [PATCH 6/9] Plan expression value preservation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 7 ++++--- pkg/workflow/schemas/github-workflow.json | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index dbb43b5ca6a..e9f5699b47d 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -1,4 +1,4 @@ -# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"dev","commands":["*","ace","approach-validator","archie","cloclo","craft","dependabot-burner","grumpy","matt","mergefest","nit","plan","poem-bot","ponytail","review","ruflo","scout","security-review","smoke-agent-all-merged","smoke-agent-all-none","smoke-agent-public-approved","smoke-agent-public-none","smoke-agent-scoped-approved","smoke-aider","smoke-call-workflow","smoke-checkout-pr-dispatch","smoke-claude","smoke-claude-on-copilot","smoke-codex","smoke-copilot","smoke-copilot-aoai-apikey","smoke-copilot-aoai-entra","smoke-copilot-arm","smoke-copilot-mai","smoke-copilot-sdk","smoke-copilot-small","smoke-create-cross-repo-pr","smoke-crush","smoke-cursor","smoke-deepseek-harness","smoke-gemini","smoke-github-claude","smoke-goose","smoke-kiro","smoke-multi-pr","smoke-opencode","smoke-otel-backends","smoke-pi","smoke-project","smoke-pydantic","smoke-service-ports","smoke-temporary-id","smoke-test-tools","smoke-update-cross-repo-pr","souschef","squad-plan","summarize","tidy","unbloat"],"workflows":["ace-editor","approach-validator","archie","ci-doctor","cloclo","craft","dependabot-burner","design-decision-gate","dev","grumpy-reviewer","mattpocock-skills-reviewer","mergefest","necromancer","pdf-summary","plan","poem-bot","ponytail-reviewer","pr-code-quality-reviewer","pr-nitpick-reviewer","pr-sous-chef","ruflo-backed-task","scout","security-review","skillet","smoke-agent-all-merged","smoke-agent-all-none","smoke-agent-public-approved","smoke-agent-public-none","smoke-agent-scoped-approved","smoke-aider","smoke-call-workflow","smoke-checkout-pr-dispatch","smoke-claude","smoke-claude-on-copilot","smoke-codex","smoke-copilot","smoke-copilot-aoai-apikey","smoke-copilot-aoai-entra","smoke-copilot-arm","smoke-copilot-mai","smoke-copilot-sdk","smoke-copilot-small","smoke-create-cross-repo-pr","smoke-crush","smoke-cursor","smoke-deepseek-harness","smoke-gemini","smoke-github-claude","smoke-goose","smoke-kiro","smoke-multi-pr","smoke-opencode","smoke-otel-backends","smoke-pi","smoke-project","smoke-pydantic","smoke-service-ports","smoke-temporary-id","smoke-test-tools","smoke-update-cross-repo-pr","squad-plan","test-quality-sentinel","tidy","unbloat-docs"]} +# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"dev","commands":["*","ace","approach-validator","archie","cloclo","craft","dependabot-burner","grumpy","matt","mergefest","nit","plan","poem-bot","ponytail","review","ruflo","scout","security-review","smoke-agent-all-merged","smoke-agent-all-none","smoke-agent-public-approved","smoke-agent-public-none","smoke-agent-scoped-approved","smoke-aider","smoke-call-workflow","smoke-checkout-pr-dispatch","smoke-claude","smoke-claude-on-copilot","smoke-codex","smoke-copilot","smoke-copilot-aoai-apikey","smoke-copilot-aoai-entra","smoke-copilot-arm","smoke-copilot-mai","smoke-copilot-sdk","smoke-copilot-small","smoke-create-cross-repo-pr","smoke-crush","smoke-cursor","smoke-deepseek-harness","smoke-drive","smoke-gemini","smoke-github-claude","smoke-goose","smoke-kiro","smoke-multi-pr","smoke-opencode","smoke-otel-backends","smoke-pi","smoke-project","smoke-pydantic","smoke-service-ports","smoke-temporary-id","smoke-test-tools","smoke-update-cross-repo-pr","souschef","squad-plan","summarize","tidy","unbloat"],"workflows":["ace-editor","approach-validator","archie","ci-doctor","cloclo","craft","dependabot-burner","design-decision-gate","dev","grumpy-reviewer","mattpocock-skills-reviewer","mergefest","necromancer","pdf-summary","plan","poem-bot","ponytail-reviewer","pr-code-quality-reviewer","pr-nitpick-reviewer","pr-sous-chef","ruflo-backed-task","scout","security-review","skillet","smoke-agent-all-merged","smoke-agent-all-none","smoke-agent-public-approved","smoke-agent-public-none","smoke-agent-scoped-approved","smoke-aider","smoke-call-workflow","smoke-checkout-pr-dispatch","smoke-claude","smoke-claude-on-copilot","smoke-codex","smoke-copilot","smoke-copilot-aoai-apikey","smoke-copilot-aoai-entra","smoke-copilot-arm","smoke-copilot-mai","smoke-copilot-sdk","smoke-copilot-small","smoke-create-cross-repo-pr","smoke-crush","smoke-cursor","smoke-deepseek-harness","smoke-drive","smoke-gemini","smoke-github-claude","smoke-goose","smoke-kiro","smoke-multi-pr","smoke-opencode","smoke-otel-backends","smoke-pi","smoke-project","smoke-pydantic","smoke-service-ports","smoke-temporary-id","smoke-test-tools","smoke-update-cross-repo-pr","squad-plan","test-quality-sentinel","tidy","unbloat-docs"]} # Routing summary (sorted): # slash commands: # /* -> skillet [pull_request_comment,pull_request_review_comment] reaction=eyes @@ -43,6 +43,7 @@ # /smoke-crush -> smoke-crush [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes # /smoke-cursor -> smoke-cursor [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket # /smoke-deepseek-harness -> smoke-deepseek-harness [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-drive -> smoke-drive [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket # /smoke-gemini -> smoke-gemini [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket # /smoke-github-claude -> smoke-github-claude [pull_request,pull_request_comment] reaction=eyes # /smoke-goose -> smoke-goose [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket @@ -141,9 +142,9 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # runner-guard:ignore RGS-016 -- routing tables below contain emoji variation selectors (U+FE0F) and zero-width joiners (U+200D) used to render standard emoji sequences, not steganographic payloads. env: - GH_AW_SLASH_ROUTING: '{"*":[{"workflow":"skillet","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿณ","status_comment":true}],"ace":[{"workflow":"ace-editor","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"โœ๏ธ","status_comment":true}],"approach-validator":[{"workflow":"approach-validator","events":["issue_comment","pull_request_comment"],"ai_reaction":"eyes","emoji":"โœ…","status_comment":true}],"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ›๏ธ","status_comment":true}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ“Š","status_comment":true}],"craft":[{"workflow":"craft","events":["issues"],"ai_reaction":"eyes","emoji":"โœ๏ธ","status_comment":true}],"dependabot-burner":[{"workflow":"dependabot-burner","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”ฅ","status_comment":true}],"grumpy":[{"workflow":"grumpy-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”","status_comment":true}],"matt":[{"workflow":"mattpocock-skills-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”","status_comment":true}],"mergefest":[{"workflow":"mergefest","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”€","status_comment":true}],"nit":[{"workflow":"pr-nitpick-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”","status_comment":true}],"plan":[{"workflow":"plan","events":["discussion_comment","issue_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ“‹","status_comment":true}],"poem-bot":[{"workflow":"poem-bot","events":["issues"],"ai_reaction":"eyes","emoji":"๐ŸŽญ","status_comment":true}],"ponytail":[{"workflow":"ponytail-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"โœ‚๏ธ","status_comment":true}],"review":[{"workflow":"design-decision-gate","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ—๏ธ","status_comment":true},{"workflow":"pr-code-quality-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”","status_comment":true},{"workflow":"test-quality-sentinel","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"ruflo":[{"workflow":"ruflo-backed-task","events":["issue_comment"],"ai_reaction":"eyes","status_comment":true}],"scout":[{"workflow":"scout","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”ญ","status_comment":true}],"security-review":[{"workflow":"security-review","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”’","status_comment":true}],"smoke-agent-all-merged":[{"workflow":"smoke-agent-all-merged","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-agent-all-none":[{"workflow":"smoke-agent-all-none","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-agent-public-approved":[{"workflow":"smoke-agent-public-approved","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-agent-public-none":[{"workflow":"smoke-agent-public-none","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-agent-scoped-approved":[{"workflow":"smoke-agent-scoped-approved","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-aider":[{"workflow":"smoke-aider","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿง‘โ€โœˆ๏ธ","status_comment":true}],"smoke-call-workflow":[{"workflow":"smoke-call-workflow","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-checkout-pr-dispatch":[{"workflow":"smoke-checkout-pr-dispatch","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-claude":[{"workflow":"smoke-claude","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"heart","emoji":"๐Ÿงช","status_comment":true}],"smoke-claude-on-copilot":[{"workflow":"smoke-claude-on-copilot","events":["pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-codex":[{"workflow":"smoke-codex","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"hooray","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot":[{"workflow":"smoke-copilot","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot-aoai-apikey":[{"workflow":"smoke-copilot-aoai-apikey","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot-aoai-entra":[{"workflow":"smoke-copilot-aoai-entra","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot-arm":[{"workflow":"smoke-copilot-arm","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot-mai":[{"workflow":"smoke-copilot-mai","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"โšก","status_comment":true}],"smoke-copilot-sdk":[{"workflow":"smoke-copilot-sdk","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”ฌ","status_comment":true}],"smoke-copilot-small":[{"workflow":"smoke-copilot-small","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿชถ","status_comment":true}],"smoke-create-cross-repo-pr":[{"workflow":"smoke-create-cross-repo-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-crush":[{"workflow":"smoke-crush","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-cursor":[{"workflow":"smoke-cursor","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿ–ฑ๏ธ","status_comment":true}],"smoke-deepseek-harness":[{"workflow":"smoke-deepseek-harness","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-gemini":[{"workflow":"smoke-gemini","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿงช","status_comment":true}],"smoke-github-claude":[{"workflow":"smoke-github-claude","events":["pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-goose":[{"workflow":"smoke-goose","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿชฟ","status_comment":true}],"smoke-kiro":[{"workflow":"smoke-kiro","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿงญ","status_comment":true}],"smoke-multi-pr":[{"workflow":"smoke-multi-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-opencode":[{"workflow":"smoke-opencode","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿงช","status_comment":true}],"smoke-otel-backends":[{"workflow":"smoke-otel-backends","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-pi":[{"workflow":"smoke-pi","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿงช","status_comment":true}],"smoke-project":[{"workflow":"smoke-project","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-pydantic":[{"workflow":"smoke-pydantic","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿ","status_comment":true}],"smoke-service-ports":[{"workflow":"smoke-service-ports","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-temporary-id":[{"workflow":"smoke-temporary-id","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-test-tools":[{"workflow":"smoke-test-tools","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-update-cross-repo-pr":[{"workflow":"smoke-update-cross-repo-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"souschef":[{"workflow":"pr-sous-chef","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ‘จโ€๐Ÿณ","status_comment":true}],"squad-plan":[{"workflow":"squad-plan","events":["issue_comment"],"ai_reaction":"eyes","emoji":"๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘","status_comment":true}],"summarize":[{"workflow":"pdf-summary","events":["issue_comment","issues"],"ai_reaction":"eyes","emoji":"๐Ÿ“„","status_comment":true}],"tidy":[{"workflow":"tidy","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงน","status_comment":true}],"unbloat":[{"workflow":"unbloat-docs","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ“","status_comment":true}]}' + GH_AW_SLASH_ROUTING: '{"*":[{"workflow":"skillet","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿณ","status_comment":true}],"ace":[{"workflow":"ace-editor","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"โœ๏ธ","status_comment":true}],"approach-validator":[{"workflow":"approach-validator","events":["issue_comment","pull_request_comment"],"ai_reaction":"eyes","emoji":"โœ…","status_comment":true}],"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ›๏ธ","status_comment":true}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ“Š","status_comment":true}],"craft":[{"workflow":"craft","events":["issues"],"ai_reaction":"eyes","emoji":"โœ๏ธ","status_comment":true}],"dependabot-burner":[{"workflow":"dependabot-burner","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”ฅ","status_comment":true}],"grumpy":[{"workflow":"grumpy-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”","status_comment":true}],"matt":[{"workflow":"mattpocock-skills-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”","status_comment":true}],"mergefest":[{"workflow":"mergefest","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”€","status_comment":true}],"nit":[{"workflow":"pr-nitpick-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”","status_comment":true}],"plan":[{"workflow":"plan","events":["discussion_comment","issue_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ“‹","status_comment":true}],"poem-bot":[{"workflow":"poem-bot","events":["issues"],"ai_reaction":"eyes","emoji":"๐ŸŽญ","status_comment":true}],"ponytail":[{"workflow":"ponytail-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"โœ‚๏ธ","status_comment":true}],"review":[{"workflow":"design-decision-gate","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ—๏ธ","status_comment":true},{"workflow":"pr-code-quality-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”","status_comment":true},{"workflow":"test-quality-sentinel","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"ruflo":[{"workflow":"ruflo-backed-task","events":["issue_comment"],"ai_reaction":"eyes","status_comment":true}],"scout":[{"workflow":"scout","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”ญ","status_comment":true}],"security-review":[{"workflow":"security-review","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”’","status_comment":true}],"smoke-agent-all-merged":[{"workflow":"smoke-agent-all-merged","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-agent-all-none":[{"workflow":"smoke-agent-all-none","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-agent-public-approved":[{"workflow":"smoke-agent-public-approved","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-agent-public-none":[{"workflow":"smoke-agent-public-none","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-agent-scoped-approved":[{"workflow":"smoke-agent-scoped-approved","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-aider":[{"workflow":"smoke-aider","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿง‘โ€โœˆ๏ธ","status_comment":true}],"smoke-call-workflow":[{"workflow":"smoke-call-workflow","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-checkout-pr-dispatch":[{"workflow":"smoke-checkout-pr-dispatch","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-claude":[{"workflow":"smoke-claude","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"heart","emoji":"๐Ÿงช","status_comment":true}],"smoke-claude-on-copilot":[{"workflow":"smoke-claude-on-copilot","events":["pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-codex":[{"workflow":"smoke-codex","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"hooray","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot":[{"workflow":"smoke-copilot","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot-aoai-apikey":[{"workflow":"smoke-copilot-aoai-apikey","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot-aoai-entra":[{"workflow":"smoke-copilot-aoai-entra","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot-arm":[{"workflow":"smoke-copilot-arm","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-copilot-mai":[{"workflow":"smoke-copilot-mai","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"โšก","status_comment":true}],"smoke-copilot-sdk":[{"workflow":"smoke-copilot-sdk","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ”ฌ","status_comment":true}],"smoke-copilot-small":[{"workflow":"smoke-copilot-small","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿชถ","status_comment":true}],"smoke-create-cross-repo-pr":[{"workflow":"smoke-create-cross-repo-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-crush":[{"workflow":"smoke-crush","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-cursor":[{"workflow":"smoke-cursor","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿ–ฑ๏ธ","status_comment":true}],"smoke-deepseek-harness":[{"workflow":"smoke-deepseek-harness","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-drive":[{"workflow":"smoke-drive","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿ’พ","status_comment":true}],"smoke-gemini":[{"workflow":"smoke-gemini","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿงช","status_comment":true}],"smoke-github-claude":[{"workflow":"smoke-github-claude","events":["pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-goose":[{"workflow":"smoke-goose","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿชฟ","status_comment":true}],"smoke-kiro":[{"workflow":"smoke-kiro","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿงญ","status_comment":true}],"smoke-multi-pr":[{"workflow":"smoke-multi-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-opencode":[{"workflow":"smoke-opencode","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿงช","status_comment":true}],"smoke-otel-backends":[{"workflow":"smoke-otel-backends","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-pi":[{"workflow":"smoke-pi","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿงช","status_comment":true}],"smoke-project":[{"workflow":"smoke-project","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-pydantic":[{"workflow":"smoke-pydantic","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket","emoji":"๐Ÿ","status_comment":true}],"smoke-service-ports":[{"workflow":"smoke-service-ports","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-temporary-id":[{"workflow":"smoke-temporary-id","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-test-tools":[{"workflow":"smoke-test-tools","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-update-cross-repo-pr":[{"workflow":"smoke-update-cross-repo-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"souschef":[{"workflow":"pr-sous-chef","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ‘จโ€๐Ÿณ","status_comment":true}],"squad-plan":[{"workflow":"squad-plan","events":["issue_comment"],"ai_reaction":"eyes","emoji":"๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘","status_comment":true}],"summarize":[{"workflow":"pdf-summary","events":["issue_comment","issues"],"ai_reaction":"eyes","emoji":"๐Ÿ“„","status_comment":true}],"tidy":[{"workflow":"tidy","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿงน","status_comment":true}],"unbloat":[{"workflow":"unbloat-docs","events":["pull_request_comment"],"ai_reaction":"eyes","emoji":"๐Ÿ“","status_comment":true}]}' GH_AW_LABEL_ROUTING: '{"approach-proposal":[{"workflow":"approach-validator","events":["issues","pull_request"],"ai_reaction":"eyes","emoji":"โœ…","status_comment":true}],"ci-doctor":[{"workflow":"ci-doctor","events":["pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿฅ","status_comment":true}],"cloclo":[{"workflow":"cloclo","events":["discussion","issues","pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿ“Š","status_comment":true}],"dev":[{"workflow":"dev","events":["discussion","issues","pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿ’ป","status_comment":true}],"necromancer":[{"workflow":"necromancer","events":["pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿ’€","status_comment":true}],"needs-design":[{"workflow":"approach-validator","events":["issues","pull_request"],"ai_reaction":"eyes","emoji":"โœ…","status_comment":true}],"smoke":[{"workflow":"smoke-copilot","events":["pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true},{"workflow":"smoke-copilot-aoai-apikey","events":["pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true},{"workflow":"smoke-copilot-aoai-entra","events":["pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true},{"workflow":"smoke-copilot-mai","events":["pull_request"],"ai_reaction":"eyes","emoji":"โšก","status_comment":true},{"workflow":"smoke-copilot-small","events":["pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿชถ","status_comment":true},{"workflow":"smoke-otel-backends","events":["pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿงช","status_comment":true}],"smoke-sdk":[{"workflow":"smoke-copilot-sdk","events":["pull_request"],"ai_reaction":"eyes","emoji":"๐Ÿ”ฌ","status_comment":true}]}' - GH_AW_HELP_COMMANDS: '[{"command":"*","description":"Reviews pull requests by mapping any slash command to a matching repository skill under .github/skills","centralized":true,"decentralized":false,"source_file":"skillet"},{"command":"ace","description":"Generates an ACE editor session link when invoked with /ace command on pull request comments","centralized":true,"decentralized":false,"source_file":"ace-editor"},{"command":"approach-validator","description":"Validates proposed technical approaches before implementation begins using a sequential multi-agent panel of Devil''s Advocate, Alternatives Scout, Implementation Estimator, and Dead End Detector","centralized":true,"decentralized":false,"source_file":"approach-validator"},{"command":"archie","description":"Generates Mermaid diagrams to visualize issue and pull request relationships when invoked with the /archie command","centralized":true,"decentralized":false,"source_file":"archie"},{"command":"cloclo","centralized":true,"decentralized":false,"source_file":"cloclo"},{"command":"craft","description":"Generates new agentic workflow markdown files based on user requests when invoked with /craft command","centralized":true,"decentralized":false,"source_file":"craft"},{"command":"dependabot-burner","description":"Runs one grouped Dependabot remediation wave from schedule, manual dispatch, or /dependabot-burner on pull requests","centralized":true,"decentralized":false,"source_file":"dependabot-burner"},{"command":"grumpy","description":"โš ๏ธ DEPRECATED: Use PR Code Quality Reviewer (pr-code-quality-reviewer) instead. Performs critical code review with a focus on edge cases, potential bugs, and code quality issues","centralized":true,"decentralized":false,"source_file":"grumpy-reviewer"},{"command":"matt","description":"Reviews pull requests using Matt Pocock''s engineering skills to provide targeted, high-quality improvement suggestions based on the type of changes","centralized":true,"decentralized":false,"source_file":"mattpocock-skills-reviewer"},{"command":"mergefest","description":"Automatically merges the main branch into pull request branches when invoked with /mergefest command","centralized":true,"decentralized":false,"source_file":"mergefest"},{"command":"nit","description":"โš ๏ธ DEPRECATED: Use PR Code Quality Reviewer (pr-code-quality-reviewer) instead. Provides detailed nitpicky code review focusing on style, best practices, and minor improvements","centralized":true,"decentralized":false,"source_file":"pr-nitpick-reviewer"},{"command":"plan","description":"Generates project plans and task breakdowns when invoked with /plan command in issues or PRs","centralized":true,"decentralized":false,"source_file":"plan"},{"command":"poem-bot","description":"Generates creative poems on specified themes when invoked with /poem-bot command","centralized":true,"decentralized":false,"source_file":"poem-bot"},{"command":"ponytail","description":"Reviews pull requests for unnecessary complexity using Ponytail","centralized":true,"decentralized":false,"source_file":"ponytail-reviewer"},{"command":"q","description":"Intelligent assistant that answers questions, analyzes repositories, and can create PRs for workflow optimizations","centralized":false,"decentralized":true,"source_file":"q"},{"command":"review","description":"Enforces Architecture Decision Records (ADRs) before implementation work can merge, detecting missing design decisions and generating draft ADRs using AI analysis","centralized":true,"decentralized":false,"source_file":"design-decision-gate"},{"command":"ruflo","description":"Runs a repository task inside GitHub Agentic Workflows while delegating inner planning and coordination to Ruflo","centralized":true,"decentralized":false,"source_file":"ruflo-backed-task"},{"command":"scout","description":"Performs deep research investigations using web search to gather and synthesize comprehensive information on any topic","centralized":true,"decentralized":false,"source_file":"scout"},{"command":"security-review","description":"Security-focused AI agent that reviews pull requests to identify changes that could weaken security posture or extend AWF boundaries","centralized":true,"decentralized":false,"source_file":"security-review"},{"command":"smoke-agent-all-merged","description":"Guard policy smoke test: repos=all, min-integrity=merged (most restrictive)","centralized":true,"decentralized":false,"source_file":"smoke-agent-all-merged"},{"command":"smoke-agent-all-none","description":"Guard policy smoke test: repos=all, min-integrity=none (most permissive)","centralized":true,"decentralized":false,"source_file":"smoke-agent-all-none"},{"command":"smoke-agent-public-approved","description":"Smoke test that validates assign-to-agent with the agentic-workflows custom agent","centralized":true,"decentralized":false,"source_file":"smoke-agent-public-approved"},{"command":"smoke-agent-public-none","description":"Guard policy smoke test: repos=public, min-integrity=none","centralized":true,"decentralized":false,"source_file":"smoke-agent-public-none"},{"command":"smoke-agent-scoped-approved","description":"Guard policy smoke test: repos=[github/gh-aw, github/*], min-integrity=approved (scoped patterns)","centralized":true,"decentralized":false,"source_file":"smoke-agent-scoped-approved"},{"command":"smoke-aider","description":"Smoke test workflow that validates Aider engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-aider"},{"command":"smoke-call-workflow","description":"Smoke test for the call-workflow safe output - orchestrator that calls a worker via workflow_call at compile-time fan-out","centralized":true,"decentralized":false,"source_file":"smoke-call-workflow"},{"command":"smoke-checkout-pr-dispatch","description":"Integration test validating that workflow_dispatch events with aw_context.item_type == ''pull_request'' correctly check out the PR branch","centralized":true,"decentralized":false,"source_file":"smoke-checkout-pr-dispatch"},{"command":"smoke-claude","description":"Smoke test workflow that validates Claude engine functionality by reviewing recent PRs twice daily","centralized":true,"decentralized":false,"source_file":"smoke-claude"},{"command":"smoke-claude-on-copilot","description":"Smoke test for Claude engine on GitHub Inference that posts a concise PR summary comment","centralized":true,"decentralized":false,"source_file":"smoke-claude-on-copilot"},{"command":"smoke-codex","description":"Smoke test workflow that validates Codex engine functionality by reviewing recent PRs twice daily","centralized":true,"decentralized":false,"source_file":"smoke-codex"},{"command":"smoke-copilot","description":"Smoke Copilot","centralized":true,"decentralized":false,"source_file":"smoke-copilot"},{"command":"smoke-copilot-aoai-apikey","description":"Smoke Copilot - AOAI (apikey)","centralized":true,"decentralized":false,"source_file":"smoke-copilot-aoai-apikey"},{"command":"smoke-copilot-aoai-entra","description":"Smoke Copilot - AOAI (Entra)","centralized":true,"decentralized":false,"source_file":"smoke-copilot-aoai-entra"},{"command":"smoke-copilot-arm","description":"Smoke Copilot ARM64","centralized":true,"decentralized":false,"source_file":"smoke-copilot-arm"},{"command":"smoke-copilot-mai","description":"Smoke test for MAI-Code-1-Flash (mai-code-1-flash-picker) โ€” pricing: $0.75/M input, $0.075/M cached, $4.50/M output","centralized":true,"decentralized":false,"source_file":"smoke-copilot-mai"},{"command":"smoke-copilot-sdk","description":"Smoke Copilot SDK","centralized":true,"decentralized":false,"source_file":"smoke-copilot-sdk"},{"command":"smoke-copilot-small","description":"Smoke Copilot Small","centralized":true,"decentralized":false,"source_file":"smoke-copilot-small"},{"command":"smoke-create-cross-repo-pr","description":"Smoke test validating cross-repo pull request creation in github/gh-aw-side-repo","centralized":true,"decentralized":false,"source_file":"smoke-create-cross-repo-pr"},{"command":"smoke-crush","description":"Smoke test workflow that validates Crush engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-crush"},{"command":"smoke-cursor","description":"Smoke test workflow that validates Cursor engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-cursor"},{"command":"smoke-deepseek-harness","description":"Smoke test workflow that validates DeepSeek Harness engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-deepseek-harness"},{"command":"smoke-gemini","description":"Smoke test workflow that validates Gemini engine functionality twice daily","centralized":true,"decentralized":false,"source_file":"smoke-gemini"},{"command":"smoke-github-claude","description":"Smoke test for Claude engine using GitHub provider that posts a concise PR summary comment","centralized":true,"decentralized":false,"source_file":"smoke-github-claude"},{"command":"smoke-goose","description":"Smoke test workflow that validates Goose engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-goose"},{"command":"smoke-kiro","description":"Smoke test workflow that validates Kiro engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-kiro"},{"command":"smoke-multi-pr","description":"Test creating multiple pull requests in a single workflow run","centralized":true,"decentralized":false,"source_file":"smoke-multi-pr"},{"command":"smoke-opencode","description":"Smoke test workflow that validates OpenCode engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-opencode"},{"command":"smoke-otel-backends","description":"Smoke test that validates OTEL span export and query access for Sentry, Grafana, and Datadog","centralized":true,"decentralized":false,"source_file":"smoke-otel-backends"},{"command":"smoke-pi","description":"Smoke test workflow that validates Pi engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-pi"},{"command":"smoke-project","description":"Smoke Project - Test project operations","centralized":true,"decentralized":false,"source_file":"smoke-project"},{"command":"smoke-pydantic","description":"Smoke test workflow that validates Pydantic AI engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-pydantic"},{"command":"smoke-service-ports","description":"Smoke test to validate --allow-host-service-ports with Redis service container","centralized":true,"decentralized":false,"source_file":"smoke-service-ports"},{"command":"smoke-temporary-id","description":"Test temporary ID functionality for issue chaining and cross-references","centralized":true,"decentralized":false,"source_file":"smoke-temporary-id"},{"command":"smoke-test-tools","description":"Smoke test to validate common development tools are available in the agent container","centralized":true,"decentralized":false,"source_file":"smoke-test-tools"},{"command":"smoke-update-cross-repo-pr","description":"Smoke test validating cross-repo pull request updates in github/gh-aw-side-repo by adding lines from Homer''s Odyssey to the README","centralized":true,"decentralized":false,"source_file":"smoke-update-cross-repo-pr"},{"command":"souschef","description":"Keeps open non-draft PRs moving toward maintainer investigation by posting targeted Copilot nudges","centralized":true,"decentralized":false,"source_file":"pr-sous-chef"},{"command":"squad","description":"Cast, connect, or adopt a Squad AI team for your repository","centralized":false,"decentralized":true,"source_file":"squad"},{"command":"squad-plan","description":"Uses Squad to plan an issue from the /squad-plan slash command and create Copilot-ready sub-issues","centralized":true,"decentralized":false,"source_file":"squad-plan"},{"command":"summarize","description":"pdf summarizer","centralized":true,"decentralized":false,"source_file":"pdf-summary"},{"command":"tidy","description":"Automatically formats and tidies code files (Go, JS, TypeScript) on schedule or command","centralized":true,"decentralized":false,"source_file":"tidy"},{"command":"unbloat","description":"Reviews and simplifies documentation by reducing verbosity while maintaining clarity and completeness","centralized":true,"decentralized":false,"source_file":"unbloat-docs"},{"command":"approach-proposal","description":"Validates proposed technical approaches before implementation begins using a sequential multi-agent panel of Devil''s Advocate, Alternatives Scout, Implementation Estimator, and Dead End Detector","centralized":false,"decentralized":false,"label":true,"source_file":"approach-validator"},{"command":"ci-doctor","description":"Investigates failed CI workflows to identify root causes and patterns, creating issues with diagnostic information; also reviews PR check failures when the ci-doctor label is applied","centralized":false,"decentralized":false,"label":true,"source_file":"ci-doctor"},{"command":"cloclo","centralized":false,"decentralized":false,"label":true,"source_file":"cloclo"},{"command":"dev","description":"Daily status report for gh-aw project","centralized":false,"decentralized":false,"label":true,"source_file":"dev"},{"command":"necromancer","description":"Investigates merge-ready pull requests, traces root-cause issues, and adds regression tests before merge","centralized":false,"decentralized":false,"label":true,"source_file":"necromancer"},{"command":"needs-design","description":"Validates proposed technical approaches before implementation begins using a sequential multi-agent panel of Devil''s Advocate, Alternatives Scout, Implementation Estimator, and Dead End Detector","centralized":false,"decentralized":false,"label":true,"source_file":"approach-validator"},{"command":"smoke","description":"Smoke Copilot - AOAI (apikey)","centralized":false,"decentralized":false,"label":true,"source_file":"smoke-copilot-aoai-apikey"},{"command":"smoke-sdk","description":"Smoke Copilot SDK","centralized":false,"decentralized":false,"label":true,"source_file":"smoke-copilot-sdk"}]' + GH_AW_HELP_COMMANDS: '[{"command":"*","description":"Reviews pull requests by mapping any slash command to a matching repository skill under .github/skills","centralized":true,"decentralized":false,"source_file":"skillet"},{"command":"ace","description":"Generates an ACE editor session link when invoked with /ace command on pull request comments","centralized":true,"decentralized":false,"source_file":"ace-editor"},{"command":"approach-validator","description":"Validates proposed technical approaches before implementation begins using a sequential multi-agent panel of Devil''s Advocate, Alternatives Scout, Implementation Estimator, and Dead End Detector","centralized":true,"decentralized":false,"source_file":"approach-validator"},{"command":"archie","description":"Generates Mermaid diagrams to visualize issue and pull request relationships when invoked with the /archie command","centralized":true,"decentralized":false,"source_file":"archie"},{"command":"cloclo","centralized":true,"decentralized":false,"source_file":"cloclo"},{"command":"craft","description":"Generates new agentic workflow markdown files based on user requests when invoked with /craft command","centralized":true,"decentralized":false,"source_file":"craft"},{"command":"dependabot-burner","description":"Runs one grouped Dependabot remediation wave from schedule, manual dispatch, or /dependabot-burner on pull requests","centralized":true,"decentralized":false,"source_file":"dependabot-burner"},{"command":"grumpy","description":"โš ๏ธ DEPRECATED: Use PR Code Quality Reviewer (pr-code-quality-reviewer) instead. Performs critical code review with a focus on edge cases, potential bugs, and code quality issues","centralized":true,"decentralized":false,"source_file":"grumpy-reviewer"},{"command":"matt","description":"Reviews pull requests using Matt Pocock''s engineering skills to provide targeted, high-quality improvement suggestions based on the type of changes","centralized":true,"decentralized":false,"source_file":"mattpocock-skills-reviewer"},{"command":"mergefest","description":"Automatically merges the main branch into pull request branches when invoked with /mergefest command","centralized":true,"decentralized":false,"source_file":"mergefest"},{"command":"nit","description":"โš ๏ธ DEPRECATED: Use PR Code Quality Reviewer (pr-code-quality-reviewer) instead. Provides detailed nitpicky code review focusing on style, best practices, and minor improvements","centralized":true,"decentralized":false,"source_file":"pr-nitpick-reviewer"},{"command":"plan","description":"Generates project plans and task breakdowns when invoked with /plan command in issues or PRs","centralized":true,"decentralized":false,"source_file":"plan"},{"command":"poem-bot","description":"Generates creative poems on specified themes when invoked with /poem-bot command","centralized":true,"decentralized":false,"source_file":"poem-bot"},{"command":"ponytail","description":"Reviews pull requests for unnecessary complexity using Ponytail","centralized":true,"decentralized":false,"source_file":"ponytail-reviewer"},{"command":"q","description":"Intelligent assistant that answers questions, analyzes repositories, and can create PRs for workflow optimizations","centralized":false,"decentralized":true,"source_file":"q"},{"command":"review","description":"Enforces Architecture Decision Records (ADRs) before implementation work can merge, detecting missing design decisions and generating draft ADRs using AI analysis","centralized":true,"decentralized":false,"source_file":"design-decision-gate"},{"command":"ruflo","description":"Runs a repository task inside GitHub Agentic Workflows while delegating inner planning and coordination to Ruflo","centralized":true,"decentralized":false,"source_file":"ruflo-backed-task"},{"command":"scout","description":"Performs deep research investigations using web search to gather and synthesize comprehensive information on any topic","centralized":true,"decentralized":false,"source_file":"scout"},{"command":"security-review","description":"Security-focused AI agent that reviews pull requests to identify changes that could weaken security posture or extend AWF boundaries","centralized":true,"decentralized":false,"source_file":"security-review"},{"command":"smoke-agent-all-merged","description":"Guard policy smoke test: repos=all, min-integrity=merged (most restrictive)","centralized":true,"decentralized":false,"source_file":"smoke-agent-all-merged"},{"command":"smoke-agent-all-none","description":"Guard policy smoke test: repos=all, min-integrity=none (most permissive)","centralized":true,"decentralized":false,"source_file":"smoke-agent-all-none"},{"command":"smoke-agent-public-approved","description":"Smoke test that validates assign-to-agent with the agentic-workflows custom agent","centralized":true,"decentralized":false,"source_file":"smoke-agent-public-approved"},{"command":"smoke-agent-public-none","description":"Guard policy smoke test: repos=public, min-integrity=none","centralized":true,"decentralized":false,"source_file":"smoke-agent-public-none"},{"command":"smoke-agent-scoped-approved","description":"Guard policy smoke test: repos=[github/gh-aw, github/*], min-integrity=approved (scoped patterns)","centralized":true,"decentralized":false,"source_file":"smoke-agent-scoped-approved"},{"command":"smoke-aider","description":"Smoke test workflow that validates Aider engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-aider"},{"command":"smoke-call-workflow","description":"Smoke test for the call-workflow safe output - orchestrator that calls a worker via workflow_call at compile-time fan-out","centralized":true,"decentralized":false,"source_file":"smoke-call-workflow"},{"command":"smoke-checkout-pr-dispatch","description":"Integration test validating that workflow_dispatch events with aw_context.item_type == ''pull_request'' correctly check out the PR branch","centralized":true,"decentralized":false,"source_file":"smoke-checkout-pr-dispatch"},{"command":"smoke-claude","description":"Smoke test workflow that validates Claude engine functionality by reviewing recent PRs twice daily","centralized":true,"decentralized":false,"source_file":"smoke-claude"},{"command":"smoke-claude-on-copilot","description":"Smoke test for Claude engine on GitHub Inference that posts a concise PR summary comment","centralized":true,"decentralized":false,"source_file":"smoke-claude-on-copilot"},{"command":"smoke-codex","description":"Smoke test workflow that validates Codex engine functionality by reviewing recent PRs twice daily","centralized":true,"decentralized":false,"source_file":"smoke-codex"},{"command":"smoke-copilot","description":"Smoke Copilot","centralized":true,"decentralized":false,"source_file":"smoke-copilot"},{"command":"smoke-copilot-aoai-apikey","description":"Smoke Copilot - AOAI (apikey)","centralized":true,"decentralized":false,"source_file":"smoke-copilot-aoai-apikey"},{"command":"smoke-copilot-aoai-entra","description":"Smoke Copilot - AOAI (Entra)","centralized":true,"decentralized":false,"source_file":"smoke-copilot-aoai-entra"},{"command":"smoke-copilot-arm","description":"Smoke Copilot ARM64","centralized":true,"decentralized":false,"source_file":"smoke-copilot-arm"},{"command":"smoke-copilot-mai","description":"Smoke test for MAI-Code-1-Flash (mai-code-1-flash-picker) โ€” pricing: $0.75/M input, $0.075/M cached, $4.50/M output","centralized":true,"decentralized":false,"source_file":"smoke-copilot-mai"},{"command":"smoke-copilot-sdk","description":"Smoke Copilot SDK","centralized":true,"decentralized":false,"source_file":"smoke-copilot-sdk"},{"command":"smoke-copilot-small","description":"Smoke Copilot Small","centralized":true,"decentralized":false,"source_file":"smoke-copilot-small"},{"command":"smoke-create-cross-repo-pr","description":"Smoke test validating cross-repo pull request creation in github/gh-aw-side-repo","centralized":true,"decentralized":false,"source_file":"smoke-create-cross-repo-pr"},{"command":"smoke-crush","description":"Smoke test workflow that validates Crush engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-crush"},{"command":"smoke-cursor","description":"Smoke test workflow that validates Cursor engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-cursor"},{"command":"smoke-deepseek-harness","description":"Smoke test workflow that validates DeepSeek Harness engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-deepseek-harness"},{"command":"smoke-drive","description":"Smoke test workflow that validates experimental GitHub Drives memory","centralized":true,"decentralized":false,"source_file":"smoke-drive"},{"command":"smoke-gemini","description":"Smoke test workflow that validates Gemini engine functionality twice daily","centralized":true,"decentralized":false,"source_file":"smoke-gemini"},{"command":"smoke-github-claude","description":"Smoke test for Claude engine using GitHub provider that posts a concise PR summary comment","centralized":true,"decentralized":false,"source_file":"smoke-github-claude"},{"command":"smoke-goose","description":"Smoke test workflow that validates Goose engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-goose"},{"command":"smoke-kiro","description":"Smoke test workflow that validates Kiro engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-kiro"},{"command":"smoke-multi-pr","description":"Test creating multiple pull requests in a single workflow run","centralized":true,"decentralized":false,"source_file":"smoke-multi-pr"},{"command":"smoke-opencode","description":"Smoke test workflow that validates OpenCode engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-opencode"},{"command":"smoke-otel-backends","description":"Smoke test that validates OTEL span export and query access for Sentry, Grafana, and Datadog","centralized":true,"decentralized":false,"source_file":"smoke-otel-backends"},{"command":"smoke-pi","description":"Smoke test workflow that validates Pi engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-pi"},{"command":"smoke-project","description":"Smoke Project - Test project operations","centralized":true,"decentralized":false,"source_file":"smoke-project"},{"command":"smoke-pydantic","description":"Smoke test workflow that validates Pydantic AI engine functionality","centralized":true,"decentralized":false,"source_file":"smoke-pydantic"},{"command":"smoke-service-ports","description":"Smoke test to validate --allow-host-service-ports with Redis service container","centralized":true,"decentralized":false,"source_file":"smoke-service-ports"},{"command":"smoke-temporary-id","description":"Test temporary ID functionality for issue chaining and cross-references","centralized":true,"decentralized":false,"source_file":"smoke-temporary-id"},{"command":"smoke-test-tools","description":"Smoke test to validate common development tools are available in the agent container","centralized":true,"decentralized":false,"source_file":"smoke-test-tools"},{"command":"smoke-update-cross-repo-pr","description":"Smoke test validating cross-repo pull request updates in github/gh-aw-side-repo by adding lines from Homer''s Odyssey to the README","centralized":true,"decentralized":false,"source_file":"smoke-update-cross-repo-pr"},{"command":"souschef","description":"Keeps open non-draft PRs moving toward maintainer investigation by posting targeted Copilot nudges","centralized":true,"decentralized":false,"source_file":"pr-sous-chef"},{"command":"squad","description":"Cast, connect, or adopt a Squad AI team for your repository","centralized":false,"decentralized":true,"source_file":"squad"},{"command":"squad-plan","description":"Uses Squad to plan an issue from the /squad-plan slash command and create Copilot-ready sub-issues","centralized":true,"decentralized":false,"source_file":"squad-plan"},{"command":"summarize","description":"pdf summarizer","centralized":true,"decentralized":false,"source_file":"pdf-summary"},{"command":"tidy","description":"Automatically formats and tidies code files (Go, JS, TypeScript) on schedule or command","centralized":true,"decentralized":false,"source_file":"tidy"},{"command":"unbloat","description":"Reviews and simplifies documentation by reducing verbosity while maintaining clarity and completeness","centralized":true,"decentralized":false,"source_file":"unbloat-docs"},{"command":"approach-proposal","description":"Validates proposed technical approaches before implementation begins using a sequential multi-agent panel of Devil''s Advocate, Alternatives Scout, Implementation Estimator, and Dead End Detector","centralized":false,"decentralized":false,"label":true,"source_file":"approach-validator"},{"command":"ci-doctor","description":"Investigates failed CI workflows to identify root causes and patterns, creating issues with diagnostic information; also reviews PR check failures when the ci-doctor label is applied","centralized":false,"decentralized":false,"label":true,"source_file":"ci-doctor"},{"command":"cloclo","centralized":false,"decentralized":false,"label":true,"source_file":"cloclo"},{"command":"dev","description":"Daily status report for gh-aw project","centralized":false,"decentralized":false,"label":true,"source_file":"dev"},{"command":"necromancer","description":"Investigates merge-ready pull requests, traces root-cause issues, and adds regression tests before merge","centralized":false,"decentralized":false,"label":true,"source_file":"necromancer"},{"command":"needs-design","description":"Validates proposed technical approaches before implementation begins using a sequential multi-agent panel of Devil''s Advocate, Alternatives Scout, Implementation Estimator, and Dead End Detector","centralized":false,"decentralized":false,"label":true,"source_file":"approach-validator"},{"command":"smoke","description":"Smoke Copilot - AOAI (apikey)","centralized":false,"decentralized":false,"label":true,"source_file":"smoke-copilot-aoai-apikey"},{"command":"smoke-sdk","description":"Smoke Copilot SDK","centralized":false,"decentralized":false,"label":true,"source_file":"smoke-copilot-sdk"}]' GH_AW_HELP_COMMAND_ENABLED: 'true' GH_AW_SLASH_COMMAND_DOCS_URL: 'https://github.github.com/gh-aw/reference/command-triggers/' with: diff --git a/pkg/workflow/schemas/github-workflow.json b/pkg/workflow/schemas/github-workflow.json index fd902c7129e..d155681f698 100644 --- a/pkg/workflow/schemas/github-workflow.json +++ b/pkg/workflow/schemas/github-workflow.json @@ -260,9 +260,6 @@ "discussions": { "$ref": "#/definitions/permissions-level" }, - "drives": { - "$ref": "#/definitions/permissions-level" - }, "id-token": { "$ref": "#/definitions/permissions-level" }, @@ -295,6 +292,9 @@ "type": "string", "enum": ["write", "none"] }, + "drives": { + "$ref": "#/definitions/permissions-level" + }, "vulnerability-alerts": { "type": "string", "enum": ["read", "none"] From 44687085552856cf56973ac3aa39128246b5d7ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:49:11 +0000 Subject: [PATCH 7/9] Preserve max turn cache miss expressions Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/awf_command_builder.go | 11 ++- pkg/workflow/awf_env.go | 16 +++ pkg/workflow/awf_env_test.go | 7 ++ pkg/workflow/awf_helpers.go | 19 ++-- pkg/workflow/compiler_orchestrator_engine.go | 54 +++++----- .../compiler_orchestrator_engine_test.go | 5 + pkg/workflow/engine.go | 99 ++++++++++--------- pkg/workflow/engine_config_parser.go | 10 ++ pkg/workflow/engine_config_parser_test.go | 14 ++- pkg/workflow/engine_helpers.go | 9 +- 10 files changed, 161 insertions(+), 83 deletions(-) diff --git a/pkg/workflow/awf_command_builder.go b/pkg/workflow/awf_command_builder.go index 18d408a3815..7b1e20091dc 100644 --- a/pkg/workflow/awf_command_builder.go +++ b/pkg/workflow/awf_command_builder.go @@ -39,6 +39,9 @@ func BuildAWFCommand(config AWFCommandConfig) string { if err != nil { awfHelpersLog.Printf("Warning: failed to build AWF config JSON: %v", err) } else { + if config.WorkflowData != nil && config.WorkflowData.EngineConfig != nil && config.WorkflowData.EngineConfig.MaxTurnCacheMissesExpression != "" { + awfConfigJSON = injectMaxTurnCacheMissesExpression(awfConfigJSON, fmt.Sprintf("${%s}", awfMaxTurnCacheMissesVarName)) + } configFileSetup = buildAWFConfigFileSetup(config, awfConfigJSON) expandableArgs = fmt.Sprintf("--config %q ", awfConfigRuntimePathExpr) + expandableArgs awfHelpersLog.Print("Using AWF config file (--config flag)") @@ -183,7 +186,8 @@ func appendArcDindMountSettings(expandableArgs, arcDindDockerHostProbe string, i func buildAWFConfigFileSetup(config AWFCommandConfig, awfConfigJSON string) string { maxAICreditsExportLine, updatedAWFConfigJSON := buildMaxAICreditsExport(config, awfConfigJSON) - printfArg := buildAWFConfigPrintfArg(updatedAWFConfigJSON, maxAICreditsExportLine != "") + preserveMaxTurnCacheMisses := config.WorkflowData != nil && config.WorkflowData.EngineConfig != nil && config.WorkflowData.EngineConfig.MaxTurnCacheMissesExpression != "" + printfArg := buildAWFConfigPrintfArg(updatedAWFConfigJSON, maxAICreditsExportLine != "", preserveMaxTurnCacheMisses) configFileSetup := buildConfigFilePrintfLine(printfArg) if maxAICreditsExportLine != "" { configFileSetup = maxAICreditsExportLine + "\n" + configFileSetup @@ -230,11 +234,14 @@ func buildMaxAICreditsExport(config AWFCommandConfig, awfConfigJSON string) (str return fmt.Sprintf(`%s="%s"`, awfMaxAICreditsVarName, expr), awfConfigJSON } -func buildAWFConfigPrintfArg(awfConfigJSON string, hasMaxAICreditsExport bool) string { +func buildAWFConfigPrintfArg(awfConfigJSON string, hasMaxAICreditsExport, preserveMaxTurnCacheMisses bool) string { preservedVars := make([]string, 0, 2) if hasMaxAICreditsExport { preservedVars = append(preservedVars, awfMaxAICreditsVarName) } + if preserveMaxTurnCacheMisses { + preservedVars = append(preservedVars, awfMaxTurnCacheMissesVarName) + } if strings.Contains(awfConfigJSON, awfArcDindRootPathExpr) { preservedVars = append(preservedVars, "RUNNER_TEMP") } diff --git a/pkg/workflow/awf_env.go b/pkg/workflow/awf_env.go index 262c9a0b2ff..54d1fef784a 100644 --- a/pkg/workflow/awf_env.go +++ b/pkg/workflow/awf_env.go @@ -61,6 +61,22 @@ func injectMaxAICreditsExpression(awfConfigJSON string, expr string) string { return awfConfigJSON[:valueEnd] + `,"maxAiCredits":` + expr + awfConfigJSON[valueEnd:] } +// injectMaxTurnCacheMissesExpression replaces the apiProxy maxCacheMisses +// integer with a GitHub Actions expression. +func injectMaxTurnCacheMissesExpression(awfConfigJSON string, expr string) string { + const maxTurnCacheMissesKey = `"maxCacheMisses":` + idx := strings.Index(awfConfigJSON, maxTurnCacheMissesKey) + if idx == -1 { + awfHelpersLog.Print("Warning: could not find maxCacheMisses in AWF config JSON; expression not injected") + return awfConfigJSON + } + valueEnd := idx + len(maxTurnCacheMissesKey) + for valueEnd < len(awfConfigJSON) && awfConfigJSON[valueEnd] >= '0' && awfConfigJSON[valueEnd] <= '9' { + valueEnd++ + } + return awfConfigJSON[:idx+len(maxTurnCacheMissesKey)] + expr + awfConfigJSON[valueEnd:] +} + // ComputeAWFExcludeEnvVarNames returns the list of environment variable names that must be // excluded from the agent container's visible environment via AWF's --exclude-env flag. // diff --git a/pkg/workflow/awf_env_test.go b/pkg/workflow/awf_env_test.go index 78118c19adc..76bd6daaf6e 100644 --- a/pkg/workflow/awf_env_test.go +++ b/pkg/workflow/awf_env_test.go @@ -23,6 +23,13 @@ func TestInjectMaxAICreditsExpressionWithoutMaxRunsLeavesJSONUnchanged(t *testin } } +func TestInjectMaxTurnCacheMissesExpression(t *testing.T) { + configJSON := `{"apiProxy":{"maxCacheMisses":5}}` + + got := injectMaxTurnCacheMissesExpression(configJSON, "${GH_AW_MAX_TURN_CACHE_MISSES}") + assert.Contains(t, got, `"maxCacheMisses":${GH_AW_MAX_TURN_CACHE_MISSES}`) +} + func TestApplyDefaultMaxAICreditsEnvToMapHandlesNilMap(t *testing.T) { assert.NotPanics(t, func() { applyDefaultMaxAICreditsEnvToMap(nil, nil) diff --git a/pkg/workflow/awf_helpers.go b/pkg/workflow/awf_helpers.go index f49a4b8c9e3..353edbcbd9c 100644 --- a/pkg/workflow/awf_helpers.go +++ b/pkg/workflow/awf_helpers.go @@ -17,15 +17,16 @@ import ( var awfHelpersLog = logger.New("workflow:awf_helpers") const ( - awfDockerHostVarName = "GH_AW_DOCKER_HOST" - awfToolCacheMountVarName = "GH_AW_TOOL_CACHE_MOUNT" - awfMaxAICreditsVarName = "GH_AW_MAX_AI_CREDITS" - awfConfigRuntimePathExpr = "${RUNNER_TEMP}/gh-aw/awf-config.json" - awfModelsJSONPathExpr = "/tmp/gh-aw/models.json" - awfArcDindRootPathExpr = "${RUNNER_TEMP}/gh-aw" - awfArcDindHomePathExpr = "${RUNNER_TEMP}/gh-aw/home" - awfArcDindProxyLogsDirExpr = "${RUNNER_TEMP}/gh-aw/sandbox/firewall/logs" - awfArcDindAuditDirExpr = "${RUNNER_TEMP}/gh-aw/sandbox/firewall/audit" + awfDockerHostVarName = "GH_AW_DOCKER_HOST" + awfToolCacheMountVarName = "GH_AW_TOOL_CACHE_MOUNT" + awfMaxAICreditsVarName = "GH_AW_MAX_AI_CREDITS" + awfMaxTurnCacheMissesVarName = "GH_AW_MAX_TURN_CACHE_MISSES" + awfConfigRuntimePathExpr = "${RUNNER_TEMP}/gh-aw/awf-config.json" + awfModelsJSONPathExpr = "/tmp/gh-aw/models.json" + awfArcDindRootPathExpr = "${RUNNER_TEMP}/gh-aw" + awfArcDindHomePathExpr = "${RUNNER_TEMP}/gh-aw/home" + awfArcDindProxyLogsDirExpr = "${RUNNER_TEMP}/gh-aw/sandbox/firewall/logs" + awfArcDindAuditDirExpr = "${RUNNER_TEMP}/gh-aw/sandbox/firewall/audit" // Bash regex used in [[ ... =~ ... ]] to detect TCP Docker hosts (ARC/DinD). // Any tcp:// DOCKER_HOST indicates the Docker daemon runs on a separate filesystem, // requiring --docker-host so AWF connects to the correct daemon. diff --git a/pkg/workflow/compiler_orchestrator_engine.go b/pkg/workflow/compiler_orchestrator_engine.go index bede98ad489..7e7dc695e50 100644 --- a/pkg/workflow/compiler_orchestrator_engine.go +++ b/pkg/workflow/compiler_orchestrator_engine.go @@ -37,7 +37,7 @@ type engineSetupResult struct { func (c *Compiler) setupEngineAndImports(result *parser.FrontmatterResult, cleanPath string, content []byte, markdownDir string) (*engineSetupResult, error) { orchestratorEngineLog.Printf("Setting up engine and processing imports") engineSetting, engineConfig, model := c.ExtractEngineConfig(result.Frontmatter) - preservedMaxTurns, preservedMaxAICredits, preservedMaxRuns, preservedMaxTurnCacheMisses := extractEngineBudgetLimits(engineConfig) + preservedMaxTurns, preservedMaxAICredits, preservedMaxRuns, preservedMaxTurnCacheMisses, preservedMaxTurnCacheMissesExpression := extractEngineBudgetLimits(engineConfig) if err := c.validateAndRegisterInlineEngineConfig(engineConfig); err != nil { return nil, err } @@ -73,15 +73,16 @@ func (c *Compiler) setupEngineAndImports(result *parser.FrontmatterResult, clean return nil, err } engineConfig, model = c.applyEngineImportDefaults(engineImportDefaultsOptions{ - engineConfig: engineConfig, - model: model, - engineSetting: engineSetting, - importsResult: importsResult, - importedEngineDefinitions: importedEngineDefinitions, - preservedMaxTurns: preservedMaxTurns, - preservedMaxAICredits: preservedMaxAICredits, - preservedMaxRuns: preservedMaxRuns, - preservedMaxTurnCacheMisses: preservedMaxTurnCacheMisses, + engineConfig: engineConfig, + model: model, + engineSetting: engineSetting, + importsResult: importsResult, + importedEngineDefinitions: importedEngineDefinitions, + preservedMaxTurns: preservedMaxTurns, + preservedMaxAICredits: preservedMaxAICredits, + preservedMaxRuns: preservedMaxRuns, + preservedMaxTurnCacheMisses: preservedMaxTurnCacheMisses, + preservedMaxTurnCacheMissesExpression: preservedMaxTurnCacheMissesExpression, }) agenticEngine, configSteps, err := c.resolveEngineRuntimeConfig(engineSetting, engineConfig) if err != nil { @@ -122,11 +123,11 @@ func frontmatterDeclaresImports(frontmatter map[string]any) bool { return false } -func extractEngineBudgetLimits(engineConfig *EngineConfig) (string, int64, int, int) { +func extractEngineBudgetLimits(engineConfig *EngineConfig) (string, int64, int, int, string) { if engineConfig == nil { - return "", 0, 0, 0 + return "", 0, 0, 0, "" } - return engineConfig.MaxTurns, engineConfig.MaxAICredits, engineConfig.MaxRuns, engineConfig.MaxTurnCacheMisses + return engineConfig.MaxTurns, engineConfig.MaxAICredits, engineConfig.MaxRuns, engineConfig.MaxTurnCacheMisses, engineConfig.MaxTurnCacheMissesExpression } func defaultNetworkPermissions(networkPermissions *NetworkPermissions) *NetworkPermissions { @@ -355,15 +356,16 @@ func (c *Compiler) resolveEngineFromIncludesAndImports( } type engineImportDefaultsOptions struct { - engineConfig *EngineConfig - model string - engineSetting string - importsResult *parser.ImportsResult - importedEngineDefinitions []string - preservedMaxTurns string - preservedMaxAICredits int64 - preservedMaxRuns int - preservedMaxTurnCacheMisses int + engineConfig *EngineConfig + model string + engineSetting string + importsResult *parser.ImportsResult + importedEngineDefinitions []string + preservedMaxTurns string + preservedMaxAICredits int64 + preservedMaxRuns int + preservedMaxTurnCacheMisses int + preservedMaxTurnCacheMissesExpression string } // applyEngineImportDefaults merges import-derived engine defaults into engineConfig. @@ -388,6 +390,9 @@ func (c *Compiler) applyEngineImportDefaults(opts engineImportDefaultsOptions) ( if opts.preservedMaxTurnCacheMisses > 0 { engineConfig.MaxTurnCacheMisses = opts.preservedMaxTurnCacheMisses } + if opts.preservedMaxTurnCacheMissesExpression != "" { + engineConfig.MaxTurnCacheMissesExpression = opts.preservedMaxTurnCacheMissesExpression + } if engineConfig.MaxTurns == "" && opts.importsResult.MergedMaxTurns != "" { var importedMaxTurns any if err := json.Unmarshal([]byte(opts.importsResult.MergedMaxTurns), &importedMaxTurns); err == nil { @@ -424,12 +429,15 @@ func (c *Compiler) applyEngineImportDefaults(opts engineImportDefaultsOptions) ( } } } - if engineConfig.MaxTurnCacheMisses <= 0 && opts.importsResult.MergedMaxTurnCacheMisses != "" { + if engineConfig.MaxTurnCacheMisses <= 0 && engineConfig.MaxTurnCacheMissesExpression == "" && opts.importsResult.MergedMaxTurnCacheMisses != "" { var importedMaxTurnCacheMisses any if err := json.Unmarshal([]byte(opts.importsResult.MergedMaxTurnCacheMisses), &importedMaxTurnCacheMisses); err == nil { if parsed := parseMaxTurnCacheMissesValue(importedMaxTurnCacheMisses); parsed > 0 { engineConfig.MaxTurnCacheMisses = parsed orchestratorEngineLog.Printf("Applied max-turn-cache-misses from import") + } else if expression := parseMaxTurnCacheMissesExpression(importedMaxTurnCacheMisses); expression != "" { + engineConfig.MaxTurnCacheMissesExpression = expression + orchestratorEngineLog.Printf("Applied max-turn-cache-misses expression from import") } } } diff --git a/pkg/workflow/compiler_orchestrator_engine_test.go b/pkg/workflow/compiler_orchestrator_engine_test.go index 837808a1117..49088c0d1ea 100644 --- a/pkg/workflow/compiler_orchestrator_engine_test.go +++ b/pkg/workflow/compiler_orchestrator_engine_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/github/gh-aw/pkg/parser" + "github.com/github/gh-aw/pkg/stringutil" "github.com/github/gh-aw/pkg/testutil" "github.com/github/gh-aw/pkg/workflow/compilerenv" "github.com/stretchr/testify/assert" @@ -323,6 +324,10 @@ imports: `), 0644)) require.NoError(t, NewCompiler().CompileWorkflow(testFile)) + lockContent, err := os.ReadFile(stringutil.MarkdownToLockFile(testFile)) + require.NoError(t, err) + assert.Contains(t, string(lockContent), "GH_AW_MAX_TURN_CACHE_MISSES: ${{ inputs.max_turn_cache_misses }}") + assert.Contains(t, string(lockContent), "${GH_AW_MAX_TURN_CACHE_MISSES}") } // TestSetupEngineAndImports_ImportedEngineVersionDefault verifies that a shared/imported diff --git a/pkg/workflow/engine.go b/pkg/workflow/engine.go index 37e54288743..2c9167a34e4 100644 --- a/pkg/workflow/engine.go +++ b/pkg/workflow/engine.go @@ -42,29 +42,30 @@ func toEngineEnvValueString(value any) (string, bool) { // EngineConfig represents the parsed engine configuration type EngineConfig struct { - ID string - Version string - LLMProvider LLMProvider // Inference provider override for this engine (engine.provider / engine.model-provider) - PermissionMode string - MaxTurns string - MaxToolDenials string // Maximum repeated tool denials before stopping inference (copilot SDK mode only) - MaxRuns int // Maximum number of LLM invocations per run (AWF apiProxy.maxRuns) - MaxTurnCacheMisses int // Maximum number of consecutive cache misses per run (AWF apiProxy.maxCacheMisses) - MaxContinuations int // Maximum number of continuations for autopilot mode (copilot engine only; > 1 enables --autopilot) - MaxAICredits int64 // Maximum allowed AI credits per run for AWF apiProxy firewall enforcement - Concurrency string // Agent job-level concurrency configuration (YAML format) - UserAgent string - Command string // Custom executable path (when set, skip installation steps) - HarnessScript string // Custom Node.js harness script filename (replaces engine default harness script when supported) - Driver string // Custom driver script filename or command. For the copilot engine (engine.driver), supports .js/.cjs/.mjs (Node.js), .py (Python), .ts/.mts (TypeScript), .rb (Ruby), or a bare command name. For the pi engine (engine.driver), supports .js/.cjs/.mjs or a bare basename resolved from the setup-action directory. - InlineDriver *InlineEngineDriver - Env map[string]string - Auth *EngineAuthConfig // Engine-level auth config (mapped to AWF_AUTH_* env vars for API proxy sidecar auth) - Config string - Args []string - Agent string // Agent identifier for copilot --agent flag (copilot engine only) - APITarget string // Custom API endpoint hostname (e.g., "api.acme.ghe.com" or "api.enterprise.githubcopilot.com") - Bare bool // When true, disables automatic loading of context/instructions (copilot: --no-custom-instructions, claude: --bare, codex: --no-system-prompt, gemini: GEMINI_SYSTEM_MD=/dev/null) + ID string + Version string + LLMProvider LLMProvider // Inference provider override for this engine (engine.provider / engine.model-provider) + PermissionMode string + MaxTurns string + MaxToolDenials string // Maximum repeated tool denials before stopping inference (copilot SDK mode only) + MaxRuns int // Maximum number of LLM invocations per run (AWF apiProxy.maxRuns) + MaxTurnCacheMisses int // Maximum number of consecutive cache misses per run (AWF apiProxy.maxCacheMisses) + MaxTurnCacheMissesExpression string // GitHub Actions expression for AWF apiProxy.maxCacheMisses + MaxContinuations int // Maximum number of continuations for autopilot mode (copilot engine only; > 1 enables --autopilot) + MaxAICredits int64 // Maximum allowed AI credits per run for AWF apiProxy firewall enforcement + Concurrency string // Agent job-level concurrency configuration (YAML format) + UserAgent string + Command string // Custom executable path (when set, skip installation steps) + HarnessScript string // Custom Node.js harness script filename (replaces engine default harness script when supported) + Driver string // Custom driver script filename or command. For the copilot engine (engine.driver), supports .js/.cjs/.mjs (Node.js), .py (Python), .ts/.mts (TypeScript), .rb (Ruby), or a bare command name. For the pi engine (engine.driver), supports .js/.cjs/.mjs or a bare basename resolved from the setup-action directory. + InlineDriver *InlineEngineDriver + Env map[string]string + Auth *EngineAuthConfig // Engine-level auth config (mapped to AWF_AUTH_* env vars for API proxy sidecar auth) + Config string + Args []string + Agent string // Agent identifier for copilot --agent flag (copilot engine only) + APITarget string // Custom API endpoint hostname (e.g., "api.acme.ghe.com" or "api.enterprise.githubcopilot.com") + Bare bool // When true, disables automatic loading of context/instructions (copilot: --no-custom-instructions, claude: --bare, codex: --no-system-prompt, gemini: GEMINI_SYSTEM_MD=/dev/null) // Inline definition fields (populated when engine.runtime is specified in frontmatter) IsInlineDefinition bool // true when the engine is defined inline via engine.runtime + optional engine.provider InlineProviderID string // engine.provider.id (e.g. "openai", "anthropic") @@ -183,12 +184,13 @@ type EngineNetworkConfig struct { } type engineTopLevelConfig struct { - maxTurns string - maxToolDenials string - maxAICredits int64 - maxTurnCacheMisses int - maxRuns int - model string + maxTurns string + maxToolDenials string + maxAICredits int64 + maxTurnCacheMisses int + maxTurnCacheMissesExpression string + maxRuns int + model string } // GetMaxAICredits returns the configured engine AI credits budget, falling back to the default. @@ -236,11 +238,12 @@ func (c *Compiler) ExtractEngineConfig(frontmatter map[string]any) (string, *Eng func parseTopLevelEngineConfig(frontmatter map[string]any) engineTopLevelConfig { topLevel := engineTopLevelConfig{ - maxTurns: parseMaxTurnsValue(frontmatter["max-turns"]), - maxToolDenials: parseMaxToolDenialsValue(frontmatter["max-tool-denials"]), - maxAICredits: parseMaxAICreditsValue(frontmatter["max-ai-credits"]), - maxTurnCacheMisses: parseMaxTurnCacheMissesValue(frontmatter["max-turn-cache-misses"]), - maxRuns: parseMaxRunsValue(frontmatter["max-turns"]), + maxTurns: parseMaxTurnsValue(frontmatter["max-turns"]), + maxToolDenials: parseMaxToolDenialsValue(frontmatter["max-tool-denials"]), + maxAICredits: parseMaxAICreditsValue(frontmatter["max-ai-credits"]), + maxTurnCacheMisses: parseMaxTurnCacheMissesValue(frontmatter["max-turn-cache-misses"]), + maxTurnCacheMissesExpression: parseMaxTurnCacheMissesExpression(frontmatter["max-turn-cache-misses"]), + maxRuns: parseMaxRunsValue(frontmatter["max-turns"]), } if topLevel.maxRuns == 0 { topLevel.maxRuns = parseMaxRunsValue(frontmatter["max-runs"]) @@ -252,12 +255,13 @@ func parseTopLevelEngineConfig(frontmatter map[string]any) engineTopLevelConfig func extractStringEngineConfig(engineStr string, topLevel engineTopLevelConfig) (string, *EngineConfig, string) { engineLog.Printf("Found engine in string format: %s", engineStr) return engineStr, &EngineConfig{ - ID: engineStr, - MaxTurns: topLevel.maxTurns, - MaxToolDenials: topLevel.maxToolDenials, - MaxRuns: topLevel.maxRuns, - MaxTurnCacheMisses: topLevel.maxTurnCacheMisses, - MaxAICredits: topLevel.maxAICredits, + ID: engineStr, + MaxTurns: topLevel.maxTurns, + MaxToolDenials: topLevel.maxToolDenials, + MaxRuns: topLevel.maxRuns, + MaxTurnCacheMisses: topLevel.maxTurnCacheMisses, + MaxTurnCacheMissesExpression: topLevel.maxTurnCacheMissesExpression, + MaxAICredits: topLevel.maxAICredits, }, topLevel.model } @@ -327,6 +331,7 @@ func applyInlineEngineFields(config *EngineConfig, engineObj map[string]any, top config.MaxToolDenials = topLevel.maxToolDenials config.MaxRuns = topLevel.maxRuns config.MaxTurnCacheMisses = topLevel.maxTurnCacheMisses + config.MaxTurnCacheMissesExpression = topLevel.maxTurnCacheMissesExpression config.MaxAICredits = topLevel.maxAICredits } @@ -635,18 +640,20 @@ func applyEngineTopLevelOverrides(config *EngineConfig, topLevel engineTopLevelC } config.MaxRuns = topLevel.maxRuns config.MaxTurnCacheMisses = topLevel.maxTurnCacheMisses + config.MaxTurnCacheMissesExpression = topLevel.maxTurnCacheMissesExpression config.MaxAICredits = topLevel.maxAICredits } func buildTopLevelOnlyEngineConfig(topLevel engineTopLevelConfig) (string, *EngineConfig, string) { if topLevel.maxTurns != "" || topLevel.maxToolDenials != "" || topLevel.maxAICredits != 0 || - topLevel.maxRuns > 0 || topLevel.maxTurnCacheMisses > 0 || topLevel.model != "" { + topLevel.maxRuns > 0 || topLevel.maxTurnCacheMisses > 0 || topLevel.maxTurnCacheMissesExpression != "" || topLevel.model != "" { return "", &EngineConfig{ - MaxTurns: topLevel.maxTurns, - MaxToolDenials: topLevel.maxToolDenials, - MaxRuns: topLevel.maxRuns, - MaxTurnCacheMisses: topLevel.maxTurnCacheMisses, - MaxAICredits: topLevel.maxAICredits, + MaxTurns: topLevel.maxTurns, + MaxToolDenials: topLevel.maxToolDenials, + MaxRuns: topLevel.maxRuns, + MaxTurnCacheMisses: topLevel.maxTurnCacheMisses, + MaxTurnCacheMissesExpression: topLevel.maxTurnCacheMissesExpression, + MaxAICredits: topLevel.maxAICredits, }, topLevel.model } engineLog.Print("No engine configuration found in frontmatter") diff --git a/pkg/workflow/engine_config_parser.go b/pkg/workflow/engine_config_parser.go index 4ba9655665c..bb6193ab728 100644 --- a/pkg/workflow/engine_config_parser.go +++ b/pkg/workflow/engine_config_parser.go @@ -36,6 +36,16 @@ func parseMaxTurnCacheMissesValue(raw any) int { return parsePositiveIntValue(raw, "max-turn-cache-misses") } +// parseMaxTurnCacheMissesExpression preserves a GitHub Actions expression for +// max-turn-cache-misses so it can be emitted into the generated workflow. +func parseMaxTurnCacheMissesExpression(raw any) string { + value := parseIntOrExpressionValue(raw, 1, "max-turn-cache-misses") + if isExpression(value) { + return value + } + return "" +} + // parsePositiveIntValue parses a strictly-positive integer from raw. // Delegates to parseIntOrExpressionValue for a single int-validation path. // GitHub Actions expression strings (e.g. "${{ inputs.value }}") are accepted diff --git a/pkg/workflow/engine_config_parser_test.go b/pkg/workflow/engine_config_parser_test.go index 0d2940e5276..d6eaa733e98 100644 --- a/pkg/workflow/engine_config_parser_test.go +++ b/pkg/workflow/engine_config_parser_test.go @@ -2,7 +2,11 @@ package workflow -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestParsePositiveIntValues(t *testing.T) { t.Parallel() @@ -35,6 +39,14 @@ func TestParsePositiveIntValues(t *testing.T) { } } +func TestParseMaxTurnCacheMissesExpression(t *testing.T) { + t.Parallel() + + assert.Equal(t, "${{ inputs.max_turn_cache_misses }}", parseMaxTurnCacheMissesExpression(" ${{ inputs.max_turn_cache_misses }} ")) + assert.Empty(t, parseMaxTurnCacheMissesExpression(5)) + assert.Empty(t, parseMaxTurnCacheMissesExpression("invalid")) +} + func TestParseIntOrExpressionValues(t *testing.T) { t.Parallel() diff --git a/pkg/workflow/engine_helpers.go b/pkg/workflow/engine_helpers.go index 66181b958d9..ad81e2dccef 100644 --- a/pkg/workflow/engine_helpers.go +++ b/pkg/workflow/engine_helpers.go @@ -107,10 +107,15 @@ func engineEnvHasNonEmptyValue(workflowData *WorkflowData, key string) bool { // process_runner.cjs) and by shell-based engine command prefixes so the engine spawns in // the user-specified working directory rather than the default GITHUB_WORKSPACE. func applyEngineCwdEnv(env map[string]string, workflowData *WorkflowData) { - if workflowData == nil || workflowData.EngineConfig == nil || workflowData.EngineConfig.Cwd == "" { + if workflowData == nil || workflowData.EngineConfig == nil { return } - env["GH_AW_ENGINE_CWD"] = workflowData.EngineConfig.Cwd + if workflowData.EngineConfig.Cwd != "" { + env["GH_AW_ENGINE_CWD"] = workflowData.EngineConfig.Cwd + } + if workflowData.EngineConfig.MaxTurnCacheMissesExpression != "" { + env[awfMaxTurnCacheMissesVarName] = workflowData.EngineConfig.MaxTurnCacheMissesExpression + } } // applyEngineVersionEnv sets the GH_AW_ENGINE_VERSION environment variable in the given From 00361c9116804419c629254cd8c4f339b58b7959 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:51:24 +0000 Subject: [PATCH 8/9] Guard AWF expression replacement Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/awf_env.go | 4 ++++ pkg/workflow/awf_env_test.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/pkg/workflow/awf_env.go b/pkg/workflow/awf_env.go index 54d1fef784a..16aa77166b1 100644 --- a/pkg/workflow/awf_env.go +++ b/pkg/workflow/awf_env.go @@ -74,6 +74,10 @@ func injectMaxTurnCacheMissesExpression(awfConfigJSON string, expr string) strin for valueEnd < len(awfConfigJSON) && awfConfigJSON[valueEnd] >= '0' && awfConfigJSON[valueEnd] <= '9' { valueEnd++ } + if valueEnd == idx+len(maxTurnCacheMissesKey) { + awfHelpersLog.Print("Warning: maxCacheMisses is not an integer in AWF config JSON; expression not injected") + return awfConfigJSON + } return awfConfigJSON[:idx+len(maxTurnCacheMissesKey)] + expr + awfConfigJSON[valueEnd:] } diff --git a/pkg/workflow/awf_env_test.go b/pkg/workflow/awf_env_test.go index 76bd6daaf6e..1955108bace 100644 --- a/pkg/workflow/awf_env_test.go +++ b/pkg/workflow/awf_env_test.go @@ -30,6 +30,12 @@ func TestInjectMaxTurnCacheMissesExpression(t *testing.T) { assert.Contains(t, got, `"maxCacheMisses":${GH_AW_MAX_TURN_CACHE_MISSES}`) } +func TestInjectMaxTurnCacheMissesExpressionWithInvalidValueLeavesJSONUnchanged(t *testing.T) { + configJSON := `{"apiProxy":{"maxCacheMisses":null}}` + + assert.Equal(t, configJSON, injectMaxTurnCacheMissesExpression(configJSON, "${GH_AW_MAX_TURN_CACHE_MISSES}")) +} + func TestApplyDefaultMaxAICreditsEnvToMapHandlesNilMap(t *testing.T) { assert.NotPanics(t, func() { applyDefaultMaxAICreditsEnvToMap(nil, nil) From 0fb4581b0cc7437b25e0aa90386e8ed1951df48d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 15:10:46 +0000 Subject: [PATCH 9/9] Use semantic JSON comparison in AWF test Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/workflow/awf_env_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/workflow/awf_env_test.go b/pkg/workflow/awf_env_test.go index 1955108bace..a79cde36f08 100644 --- a/pkg/workflow/awf_env_test.go +++ b/pkg/workflow/awf_env_test.go @@ -33,7 +33,7 @@ func TestInjectMaxTurnCacheMissesExpression(t *testing.T) { func TestInjectMaxTurnCacheMissesExpressionWithInvalidValueLeavesJSONUnchanged(t *testing.T) { configJSON := `{"apiProxy":{"maxCacheMisses":null}}` - assert.Equal(t, configJSON, injectMaxTurnCacheMissesExpression(configJSON, "${GH_AW_MAX_TURN_CACHE_MISSES}")) + assert.JSONEq(t, configJSON, injectMaxTurnCacheMissesExpression(configJSON, "${GH_AW_MAX_TURN_CACHE_MISSES}")) } func TestApplyDefaultMaxAICreditsEnvToMapHandlesNilMap(t *testing.T) {