diff --git a/.github/workflows/chore-style-go.lock.yml b/.github/workflows/chore-style-go.lock.yml
index 7dc8c42..ca59f3c 100644
--- a/.github/workflows/chore-style-go.lock.yml
+++ b/.github/workflows/chore-style-go.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"75ac690e8edd39c77450c4ec6cff5dd27b6b94ce07417d4a767a1402b207503f","compiler_version":"v0.74.3","agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6f1ad8d13c8e7665972c58c35e827a2a203758d94d27e89ad9d6cc89ea8b1c38","compiler_version":"v0.74.3","agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"v5","version":"v5"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# ___ _ _
# / _ \ | | (_)
@@ -235,23 +235,23 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF'
+ cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF'
- GH_AW_PROMPT_dbf277e45ce618d1_EOF
+ GH_AW_PROMPT_75feba9e090dc7aa_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF'
+ cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF'
Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop
- GH_AW_PROMPT_dbf277e45ce618d1_EOF
+ GH_AW_PROMPT_75feba9e090dc7aa_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md"
- cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF'
+ cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF'
- GH_AW_PROMPT_dbf277e45ce618d1_EOF
+ GH_AW_PROMPT_75feba9e090dc7aa_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF'
+ cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -280,9 +280,9 @@ jobs:
{{/if}}
- GH_AW_PROMPT_dbf277e45ce618d1_EOF
+ GH_AW_PROMPT_75feba9e090dc7aa_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF'
+ cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF'
@@ -765,6 +765,30 @@ jobs:
You are the Go style agent. Read `inputs.mode` and act accordingly.
+ ## Mode → safe-output contract (READ FIRST)
+
+ The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+ tool is the defect tracked in issue #30 and is the single most important
+ rule in this prompt.
+
+ - `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+ - `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+ If you find yourself about to call `create_issue` while `inputs.mode ==
+ autofix`, stop and re-read this section.
+
## Mode: report
1. `gofmt -l .` — list files needing format (empty output means clean).
@@ -802,7 +826,9 @@ jobs:
the PR**; staticcheck has no `--fix` mode and surfaces semantic issues.
- `go build ./...` — must exit 0.
- `go test ./...` — must exit 0.
- 4. Open one PR via `create-pull-request`:
+ 4. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:go] auto-applied gofmt + goimports`.
- Body: summary of files touched, `Closes #` if applicable.
- Labels: `agent:lint:go`, `agent:autofix`.
@@ -820,8 +846,10 @@ jobs:
- Do not modify `go.mod` or `go.sum`.
- Do not auto-apply go vet or staticcheck suggestions.
- Do not open more than one PR or issue per run.
+ - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+ - Do not call `create_pull_request` when `inputs.mode == report`.
- GH_AW_PROMPT_dbf277e45ce618d1_EOF
+ GH_AW_PROMPT_75feba9e090dc7aa_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1026,9 +1054,9 @@ jobs:
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7729c44e1703fffc_EOF'
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_63d6cb2d016e54fc_EOF'
{"create_issue":{"labels":["agent:lint:go"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:go","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}}
- GH_AW_SAFE_OUTPUTS_CONFIG_7729c44e1703fffc_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_63d6cb2d016e54fc_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -1333,7 +1361,7 @@ jobs:
mkdir -p /home/runner/.copilot
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_14c58b6124db2869_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_00c9308fa251b77e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
@@ -1374,7 +1402,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_14c58b6124db2869_EOF
+ GH_AW_MCP_CONFIG_00c9308fa251b77e_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
diff --git a/.github/workflows/chore-style-go.md b/.github/workflows/chore-style-go.md
index a1df259..d9dc8b3 100644
--- a/.github/workflows/chore-style-go.md
+++ b/.github/workflows/chore-style-go.md
@@ -40,6 +40,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -87,6 +98,30 @@ Behavior summary:
You are the Go style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Mode: report
1. `gofmt -l .` — list files needing format (empty output means clean).
@@ -124,7 +159,9 @@ Apply dedup before emitting.
the PR**; staticcheck has no `--fix` mode and surfaces semantic issues.
- `go build ./...` — must exit 0.
- `go test ./...` — must exit 0.
-4. Open one PR via `create-pull-request`:
+4. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:go] auto-applied gofmt + goimports`.
- Body: summary of files touched, `Closes #` if applicable.
- Labels: `agent:lint:go`, `agent:autofix`.
@@ -142,3 +179,5 @@ Apply dedup before emitting.
- Do not modify `go.mod` or `go.sum`.
- Do not auto-apply go vet or staticcheck suggestions.
- Do not open more than one PR or issue per run.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.
diff --git a/.github/workflows/chore-style-ncl.lock.yml b/.github/workflows/chore-style-ncl.lock.yml
index 7c3f8d8..f79761d 100644
--- a/.github/workflows/chore-style-ncl.lock.yml
+++ b/.github/workflows/chore-style-ncl.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"98a0b3ccd6685d3286de63b3b2ef4632457ba84e74f1efe8f5f68643d9c48d71","compiler_version":"v0.74.3","agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6b26e31c87143ef4a69781e136dd352bc7c6194086b8f4a0731099e70617be19","compiler_version":"v0.74.3","agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# ___ _ _
# / _ \ | | (_)
@@ -233,23 +233,23 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF'
+ cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF'
- GH_AW_PROMPT_7bf3b7bc267bb54a_EOF
+ GH_AW_PROMPT_dbc7efee147b3557_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF'
+ cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF'
Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop
- GH_AW_PROMPT_7bf3b7bc267bb54a_EOF
+ GH_AW_PROMPT_dbc7efee147b3557_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md"
- cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF'
+ cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF'
- GH_AW_PROMPT_7bf3b7bc267bb54a_EOF
+ GH_AW_PROMPT_dbc7efee147b3557_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF'
+ cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -278,9 +278,9 @@ jobs:
{{/if}}
- GH_AW_PROMPT_7bf3b7bc267bb54a_EOF
+ GH_AW_PROMPT_dbc7efee147b3557_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF'
+ cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF'
@@ -690,6 +690,30 @@ jobs:
You are the Nickel style agent. Read `inputs.mode` and act accordingly.
+ ## Mode → safe-output contract (READ FIRST)
+
+ The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+ tool is the defect tracked in issue #30 and is the single most important
+ rule in this prompt.
+
+ - `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+ - `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+ If you find yourself about to call `create_issue` while `inputs.mode ==
+ autofix`, stop and re-read this section.
+
## Scope
Every `*.ncl` file under the repo root, excluding paths in `.gitignore` and
@@ -726,7 +750,9 @@ jobs:
- `nickel format --check` on each file — must exit 0.
- `nickel typecheck` on each file — must exit 0. **If typecheck fails,
do not open the PR**; type errors are reported, not auto-fixed.
- 3. Open one PR via `create-pull-request`:
+ 3. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:ncl] auto-applied nickel format`.
- Body: summary of files touched, `Closes #` if applicable.
- Labels: `agent:lint:ncl`, `agent:autofix`.
@@ -744,8 +770,10 @@ jobs:
- Do not auto-apply typecheck fixes.
- Do not open more than one PR or issue per run.
- Do not modify files outside the in-scope set.
+ - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+ - Do not call `create_pull_request` when `inputs.mode == report`.
- GH_AW_PROMPT_7bf3b7bc267bb54a_EOF
+ GH_AW_PROMPT_dbc7efee147b3557_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -950,9 +978,9 @@ jobs:
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_de6a1440474a5e7a_EOF'
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2865f2ee842c780c_EOF'
{"create_issue":{"labels":["agent:lint:ncl"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:ncl","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}}
- GH_AW_SAFE_OUTPUTS_CONFIG_de6a1440474a5e7a_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_2865f2ee842c780c_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -1257,7 +1285,7 @@ jobs:
mkdir -p /home/runner/.copilot
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_1fbbe8ba343fc38b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_9cad70d2fcd0078b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
@@ -1298,7 +1326,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_1fbbe8ba343fc38b_EOF
+ GH_AW_MCP_CONFIG_9cad70d2fcd0078b_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
diff --git a/.github/workflows/chore-style-ncl.md b/.github/workflows/chore-style-ncl.md
index 4ecefaf..5c3a773 100644
--- a/.github/workflows/chore-style-ncl.md
+++ b/.github/workflows/chore-style-ncl.md
@@ -39,6 +39,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -81,6 +92,30 @@ Behavior summary:
You are the Nickel style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Scope
Every `*.ncl` file under the repo root, excluding paths in `.gitignore` and
@@ -117,7 +152,9 @@ Apply dedup before emitting.
- `nickel format --check` on each file — must exit 0.
- `nickel typecheck` on each file — must exit 0. **If typecheck fails,
do not open the PR**; type errors are reported, not auto-fixed.
-3. Open one PR via `create-pull-request`:
+3. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:ncl] auto-applied nickel format`.
- Body: summary of files touched, `Closes #` if applicable.
- Labels: `agent:lint:ncl`, `agent:autofix`.
@@ -135,3 +172,5 @@ Apply dedup before emitting.
- Do not auto-apply typecheck fixes.
- Do not open more than one PR or issue per run.
- Do not modify files outside the in-scope set.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.
diff --git a/.github/workflows/chore-style-python.lock.yml b/.github/workflows/chore-style-python.lock.yml
index b0b3d7c..d4162ca 100644
--- a/.github/workflows/chore-style-python.lock.yml
+++ b/.github/workflows/chore-style-python.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"88099074474cfc09845d8b84c7f0f97d0bb0076fcb2b039ce95c034d8a839f99","compiler_version":"v0.74.3","agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"53646a5bc5496eb158001d4acd0b58004596b7169547c21a202bd5d4946711a5","compiler_version":"v0.74.3","agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"astral-sh/setup-uv","sha":"v3","version":"v3"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# ___ _ _
# / _ \ | | (_)
@@ -235,23 +235,23 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_576e86da4707b909_EOF'
+ cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF'
- GH_AW_PROMPT_576e86da4707b909_EOF
+ GH_AW_PROMPT_2e88897e0e4c235c_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_576e86da4707b909_EOF'
+ cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF'
Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop
- GH_AW_PROMPT_576e86da4707b909_EOF
+ GH_AW_PROMPT_2e88897e0e4c235c_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md"
- cat << 'GH_AW_PROMPT_576e86da4707b909_EOF'
+ cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF'
- GH_AW_PROMPT_576e86da4707b909_EOF
+ GH_AW_PROMPT_2e88897e0e4c235c_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_576e86da4707b909_EOF'
+ cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -280,9 +280,9 @@ jobs:
{{/if}}
- GH_AW_PROMPT_576e86da4707b909_EOF
+ GH_AW_PROMPT_2e88897e0e4c235c_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_576e86da4707b909_EOF'
+ cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF'
@@ -779,6 +779,30 @@ jobs:
You are the Python style agent. Read `inputs.mode` and act accordingly.
+ ## Mode → safe-output contract (READ FIRST)
+
+ The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+ tool is the defect tracked in issue #30 and is the single most important
+ rule in this prompt.
+
+ - `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+ - `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+ If you find yourself about to call `create_issue` while `inputs.mode ==
+ autofix`, stop and re-read this section.
+
## Mode: report
1. `uv sync --frozen` to provision the environment.
@@ -816,7 +840,9 @@ jobs:
even if ruff is clean; mypy has no `--fix` mode and unfixed type
errors carry semantic risk.
- `uv run pytest` — must exit 0.
- 5. Open one PR via `create-pull-request`:
+ 5. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:python] auto-applied ruff format + lint fixes`.
- Body: summary of files touched, count of format vs lint fixes,
`Closes #` if a matching `agent:lint:python` issue is open.
@@ -836,8 +862,10 @@ jobs:
- Do not auto-apply mypy fixes (mypy has no `--fix` mode; do not synthesize one).
- Do not open more than one PR or issue per run.
- Do not skip the pytest verification step in autofix mode.
+ - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+ - Do not call `create_pull_request` when `inputs.mode == report`.
- GH_AW_PROMPT_576e86da4707b909_EOF
+ GH_AW_PROMPT_2e88897e0e4c235c_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1042,9 +1070,9 @@ jobs:
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_d330cb482b71deae_EOF'
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_db561e3d8fadb031_EOF'
{"create_issue":{"labels":["agent:lint:python"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:python","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}}
- GH_AW_SAFE_OUTPUTS_CONFIG_d330cb482b71deae_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_db561e3d8fadb031_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -1349,7 +1377,7 @@ jobs:
mkdir -p /home/runner/.copilot
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_24eeb4b18f62076a_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_85539d4595f393bc_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
@@ -1390,7 +1418,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_24eeb4b18f62076a_EOF
+ GH_AW_MCP_CONFIG_85539d4595f393bc_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
diff --git a/.github/workflows/chore-style-python.md b/.github/workflows/chore-style-python.md
index 7908fad..669a84a 100644
--- a/.github/workflows/chore-style-python.md
+++ b/.github/workflows/chore-style-python.md
@@ -40,6 +40,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -86,6 +97,30 @@ Behavior summary:
You are the Python style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Mode: report
1. `uv sync --frozen` to provision the environment.
@@ -123,7 +158,9 @@ Apply dedup before emitting.
even if ruff is clean; mypy has no `--fix` mode and unfixed type
errors carry semantic risk.
- `uv run pytest` — must exit 0.
-5. Open one PR via `create-pull-request`:
+5. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:python] auto-applied ruff format + lint fixes`.
- Body: summary of files touched, count of format vs lint fixes,
`Closes #` if a matching `agent:lint:python` issue is open.
@@ -143,3 +180,5 @@ Apply dedup before emitting.
- Do not auto-apply mypy fixes (mypy has no `--fix` mode; do not synthesize one).
- Do not open more than one PR or issue per run.
- Do not skip the pytest verification step in autofix mode.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.
diff --git a/.github/workflows/chore-style-rust.lock.yml b/.github/workflows/chore-style-rust.lock.yml
index 71fb337..cccdc97 100644
--- a/.github/workflows/chore-style-rust.lock.yml
+++ b/.github/workflows/chore-style-rust.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f47408acef61f5eb61d21a1fa6f0328e8663d0e1e9d5a3765fad55f6e2f78e18","compiler_version":"v0.74.3","agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"bf051ff65cb21982680e647a4ea074f473f49a7554800af86f9db7f59f2bc75c","compiler_version":"v0.74.3","agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"Swatinem/rust-cache","sha":"v2","version":"v2"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"dtolnay/rust-toolchain","sha":"stable","version":"stable"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"},{"repo":"taiki-e/install-action","sha":"v2","version":"v2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# ___ _ _
# / _ \ | | (_)
@@ -238,23 +238,23 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_658becfb299e6630_EOF'
+ cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF'
- GH_AW_PROMPT_658becfb299e6630_EOF
+ GH_AW_PROMPT_66d777413c34b61a_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_658becfb299e6630_EOF'
+ cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF'
Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop
- GH_AW_PROMPT_658becfb299e6630_EOF
+ GH_AW_PROMPT_66d777413c34b61a_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md"
- cat << 'GH_AW_PROMPT_658becfb299e6630_EOF'
+ cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF'
- GH_AW_PROMPT_658becfb299e6630_EOF
+ GH_AW_PROMPT_66d777413c34b61a_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_658becfb299e6630_EOF'
+ cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -283,9 +283,9 @@ jobs:
{{/if}}
- GH_AW_PROMPT_658becfb299e6630_EOF
+ GH_AW_PROMPT_66d777413c34b61a_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_658becfb299e6630_EOF'
+ cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF'
@@ -771,6 +771,30 @@ jobs:
You are the Rust style agent. Read `inputs.mode` and act accordingly.
+ ## Mode → safe-output contract (READ FIRST)
+
+ The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+ tool is the defect tracked in issue #30 and is the single most important
+ rule in this prompt.
+
+ - `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+ - `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+ If you find yourself about to call `create_issue` while `inputs.mode ==
+ autofix`, stop and re-read this section.
+
## Inputs
1. Current working tree of the default branch.
@@ -819,7 +843,9 @@ jobs:
Any non-zero exit means do not open the PR; emit `report_incomplete`
naming the failing step and stop.
- 4. Open one PR via `create-pull-request`:
+ 4. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:rust] auto-applied rustfmt + clippy fixes`.
- Body: a summary of the files touched and a count of fmt vs clippy
fixes. Include `Closes #` if an open `agent:lint:rust` issue
@@ -840,8 +866,10 @@ jobs:
- Do not run `clippy --fix` without a subsequent verification gate.
- Do not open more than one PR or issue per run.
- Do not bypass the verification gate by stashing uncommitted changes.
+ - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+ - Do not call `create_pull_request` when `inputs.mode == report`.
- GH_AW_PROMPT_658becfb299e6630_EOF
+ GH_AW_PROMPT_66d777413c34b61a_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1049,9 +1077,9 @@ jobs:
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f663d45145b79719_EOF'
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ab35a225af76be7c_EOF'
{"create_issue":{"labels":["agent:lint:rust"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:rust","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}}
- GH_AW_SAFE_OUTPUTS_CONFIG_f663d45145b79719_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_ab35a225af76be7c_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -1356,7 +1384,7 @@ jobs:
mkdir -p /home/runner/.copilot
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_4d6f5d2881c54af9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_40bf4451983d884a_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
@@ -1397,7 +1425,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_4d6f5d2881c54af9_EOF
+ GH_AW_MCP_CONFIG_40bf4451983d884a_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
diff --git a/.github/workflows/chore-style-rust.md b/.github/workflows/chore-style-rust.md
index 1e68d62..aede3d3 100644
--- a/.github/workflows/chore-style-rust.md
+++ b/.github/workflows/chore-style-rust.md
@@ -40,6 +40,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -86,6 +97,30 @@ Behavior summary:
You are the Rust style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Inputs
1. Current working tree of the default branch.
@@ -134,7 +169,9 @@ emitting; if a matching open issue exists, emit `update-issue` instead.
Any non-zero exit means do not open the PR; emit `report_incomplete`
naming the failing step and stop.
-4. Open one PR via `create-pull-request`:
+4. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:rust] auto-applied rustfmt + clippy fixes`.
- Body: a summary of the files touched and a count of fmt vs clippy
fixes. Include `Closes #` if an open `agent:lint:rust` issue
@@ -155,3 +192,5 @@ emitting; if a matching open issue exists, emit `update-issue` instead.
- Do not run `clippy --fix` without a subsequent verification gate.
- Do not open more than one PR or issue per run.
- Do not bypass the verification gate by stashing uncommitted changes.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.
diff --git a/.github/workflows/chore-style-toml.lock.yml b/.github/workflows/chore-style-toml.lock.yml
index f665cbf..32d69d8 100644
--- a/.github/workflows/chore-style-toml.lock.yml
+++ b/.github/workflows/chore-style-toml.lock.yml
@@ -1,4 +1,4 @@
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"664f75ec218887caad08d5b1d20e6fa55be1bac3eb0eb4eafa021312578d04e2","compiler_version":"v0.74.3","agent_id":"copilot"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5a0b0aa066838511a0d5828fa16ab809f9e30e25bb4da6c9f8d7b90e7b2f534b","compiler_version":"v0.74.3","agent_id":"copilot"}
# gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]}
# ___ _ _
# / _ \ | | (_)
@@ -233,23 +233,23 @@ jobs:
run: |
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
{
- cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF'
+ cat << 'GH_AW_PROMPT_812133c986f8f367_EOF'
- GH_AW_PROMPT_d4ed0db97c47a8a8_EOF
+ GH_AW_PROMPT_812133c986f8f367_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF'
+ cat << 'GH_AW_PROMPT_812133c986f8f367_EOF'
Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop
- GH_AW_PROMPT_d4ed0db97c47a8a8_EOF
+ GH_AW_PROMPT_812133c986f8f367_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md"
- cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF'
+ cat << 'GH_AW_PROMPT_812133c986f8f367_EOF'
- GH_AW_PROMPT_d4ed0db97c47a8a8_EOF
+ GH_AW_PROMPT_812133c986f8f367_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
- cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF'
+ cat << 'GH_AW_PROMPT_812133c986f8f367_EOF'
The following GitHub context information is available for this workflow:
{{#if github.actor}}
@@ -278,9 +278,9 @@ jobs:
{{/if}}
- GH_AW_PROMPT_d4ed0db97c47a8a8_EOF
+ GH_AW_PROMPT_812133c986f8f367_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF'
+ cat << 'GH_AW_PROMPT_812133c986f8f367_EOF'
@@ -686,6 +686,30 @@ jobs:
You are the TOML style agent. Read `inputs.mode` and act accordingly.
+ ## Mode → safe-output contract (READ FIRST)
+
+ The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+ tool is the defect tracked in issue #30 and is the single most important
+ rule in this prompt.
+
+ - `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+ - `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+ If you find yourself about to call `create_issue` while `inputs.mode ==
+ autofix`, stop and re-read this section.
+
## Scope
Every `*.toml` file under the repo root, excluding:
@@ -722,7 +746,9 @@ jobs:
- `taplo fmt --check` — must exit 0.
- `taplo lint` — must exit 0. If lint fails, do not open the PR
(taplo lint has no `--fix` mode for schema violations).
- 3. Open one PR via `create-pull-request`:
+ 3. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:toml] auto-applied taplo fmt`.
- Body: summary of files touched, `Closes #` if applicable.
- Labels: `agent:lint:toml`, `agent:autofix`.
@@ -740,8 +766,10 @@ jobs:
- Do not modify `Cargo.toml` or any TOML inside a Rust workspace.
- Do not auto-apply schema-violation fixes; only formatter changes.
- Do not open more than one PR or issue per run.
+ - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+ - Do not call `create_pull_request` when `inputs.mode == report`.
- GH_AW_PROMPT_d4ed0db97c47a8a8_EOF
+ GH_AW_PROMPT_812133c986f8f367_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -946,9 +974,9 @@ jobs:
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_b6cd7e6c7251f1da_EOF'
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4ee635edfe543b08_EOF'
{"create_issue":{"labels":["agent:lint:toml"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:toml","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}}
- GH_AW_SAFE_OUTPUTS_CONFIG_b6cd7e6c7251f1da_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_4ee635edfe543b08_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -1253,7 +1281,7 @@ jobs:
mkdir -p /home/runner/.copilot
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
- cat << GH_AW_MCP_CONFIG_d1848364507628af_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_443708422ec8604f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
{
"mcpServers": {
"github": {
@@ -1294,7 +1322,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_d1848364507628af_EOF
+ GH_AW_MCP_CONFIG_443708422ec8604f_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
diff --git a/.github/workflows/chore-style-toml.md b/.github/workflows/chore-style-toml.md
index aba0d22..62187d3 100644
--- a/.github/workflows/chore-style-toml.md
+++ b/.github/workflows/chore-style-toml.md
@@ -39,6 +39,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -81,6 +92,30 @@ Behavior summary:
You are the TOML style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Scope
Every `*.toml` file under the repo root, excluding:
@@ -117,7 +152,9 @@ Apply dedup before emitting.
- `taplo fmt --check` — must exit 0.
- `taplo lint` — must exit 0. If lint fails, do not open the PR
(taplo lint has no `--fix` mode for schema violations).
-3. Open one PR via `create-pull-request`:
+3. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:toml] auto-applied taplo fmt`.
- Body: summary of files touched, `Closes #` if applicable.
- Labels: `agent:lint:toml`, `agent:autofix`.
@@ -135,3 +172,5 @@ Apply dedup before emitting.
- Do not modify `Cargo.toml` or any TOML inside a Rust workspace.
- Do not auto-apply schema-violation fixes; only formatter changes.
- Do not open more than one PR or issue per run.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.
diff --git a/workflows/chore-style-go.md b/workflows/chore-style-go.md
index a1df259..d9dc8b3 100644
--- a/workflows/chore-style-go.md
+++ b/workflows/chore-style-go.md
@@ -40,6 +40,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -87,6 +98,30 @@ Behavior summary:
You are the Go style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Mode: report
1. `gofmt -l .` — list files needing format (empty output means clean).
@@ -124,7 +159,9 @@ Apply dedup before emitting.
the PR**; staticcheck has no `--fix` mode and surfaces semantic issues.
- `go build ./...` — must exit 0.
- `go test ./...` — must exit 0.
-4. Open one PR via `create-pull-request`:
+4. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:go] auto-applied gofmt + goimports`.
- Body: summary of files touched, `Closes #` if applicable.
- Labels: `agent:lint:go`, `agent:autofix`.
@@ -142,3 +179,5 @@ Apply dedup before emitting.
- Do not modify `go.mod` or `go.sum`.
- Do not auto-apply go vet or staticcheck suggestions.
- Do not open more than one PR or issue per run.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.
diff --git a/workflows/chore-style-ncl.md b/workflows/chore-style-ncl.md
index 4ecefaf..5c3a773 100644
--- a/workflows/chore-style-ncl.md
+++ b/workflows/chore-style-ncl.md
@@ -39,6 +39,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -81,6 +92,30 @@ Behavior summary:
You are the Nickel style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Scope
Every `*.ncl` file under the repo root, excluding paths in `.gitignore` and
@@ -117,7 +152,9 @@ Apply dedup before emitting.
- `nickel format --check` on each file — must exit 0.
- `nickel typecheck` on each file — must exit 0. **If typecheck fails,
do not open the PR**; type errors are reported, not auto-fixed.
-3. Open one PR via `create-pull-request`:
+3. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:ncl] auto-applied nickel format`.
- Body: summary of files touched, `Closes #` if applicable.
- Labels: `agent:lint:ncl`, `agent:autofix`.
@@ -135,3 +172,5 @@ Apply dedup before emitting.
- Do not auto-apply typecheck fixes.
- Do not open more than one PR or issue per run.
- Do not modify files outside the in-scope set.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.
diff --git a/workflows/chore-style-python.md b/workflows/chore-style-python.md
index 7908fad..669a84a 100644
--- a/workflows/chore-style-python.md
+++ b/workflows/chore-style-python.md
@@ -40,6 +40,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -86,6 +97,30 @@ Behavior summary:
You are the Python style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Mode: report
1. `uv sync --frozen` to provision the environment.
@@ -123,7 +158,9 @@ Apply dedup before emitting.
even if ruff is clean; mypy has no `--fix` mode and unfixed type
errors carry semantic risk.
- `uv run pytest` — must exit 0.
-5. Open one PR via `create-pull-request`:
+5. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:python] auto-applied ruff format + lint fixes`.
- Body: summary of files touched, count of format vs lint fixes,
`Closes #` if a matching `agent:lint:python` issue is open.
@@ -143,3 +180,5 @@ Apply dedup before emitting.
- Do not auto-apply mypy fixes (mypy has no `--fix` mode; do not synthesize one).
- Do not open more than one PR or issue per run.
- Do not skip the pytest verification step in autofix mode.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.
diff --git a/workflows/chore-style-rust.md b/workflows/chore-style-rust.md
index 1e68d62..aede3d3 100644
--- a/workflows/chore-style-rust.md
+++ b/workflows/chore-style-rust.md
@@ -40,6 +40,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -86,6 +97,30 @@ Behavior summary:
You are the Rust style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Inputs
1. Current working tree of the default branch.
@@ -134,7 +169,9 @@ emitting; if a matching open issue exists, emit `update-issue` instead.
Any non-zero exit means do not open the PR; emit `report_incomplete`
naming the failing step and stop.
-4. Open one PR via `create-pull-request`:
+4. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:rust] auto-applied rustfmt + clippy fixes`.
- Body: a summary of the files touched and a count of fmt vs clippy
fixes. Include `Closes #` if an open `agent:lint:rust` issue
@@ -155,3 +192,5 @@ emitting; if a matching open issue exists, emit `update-issue` instead.
- Do not run `clippy --fix` without a subsequent verification gate.
- Do not open more than one PR or issue per run.
- Do not bypass the verification gate by stashing uncommitted changes.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.
diff --git a/workflows/chore-style-toml.md b/workflows/chore-style-toml.md
index aba0d22..62187d3 100644
--- a/workflows/chore-style-toml.md
+++ b/workflows/chore-style-toml.md
@@ -39,6 +39,17 @@ safe-outputs:
private-key: ${{ secrets.APP_PRIVATE_KEY }}
repositories:
- ${{ github.event.repository.name }}
+ # Note (issue #30): both create-issue and create-pull-request are listed
+ # so the agent has the right tool available in either mode. The agent
+ # MUST select the tool that matches inputs.mode — see the prose contract
+ # below. Gating via templated `max` was attempted but rejected: every
+ # available gh-aw expression form for a ternary (`cond && '0' || '1'`,
+ # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON
+ # env var with characters that actionlint cannot lex (`&&` → `&&`;
+ # nested `\"` inside `fromJSON()`). The prose contract is the primary
+ # defense; if the agent calls create_issue in autofix anyway, the
+ # safe-output allowlist will still create the issue but the chore
+ # behaviour is wrong and visible from the run log.
create-issue:
max: 1
labels:
@@ -81,6 +92,30 @@ Behavior summary:
You are the TOML style agent. Read `inputs.mode` and act accordingly.
+## Mode → safe-output contract (READ FIRST)
+
+The safe-output tool you call MUST match `inputs.mode`. Picking the wrong
+tool is the defect tracked in issue #30 and is the single most important
+rule in this prompt.
+
+- `mode == report`:
+ - You MUST call `create_issue` (or `update_issue` on dedup hit).
+ - You MUST NOT call `create_pull_request`. Report mode does not modify
+ files; opening a PR is a contract violation.
+- `mode == autofix`:
+ - You MUST call `create_pull_request`.
+ - You MUST NOT call `create_issue` or `update_issue` under any
+ circumstances — not as a fallback, not to "also notify", not because
+ the verification gate failed. If the verification gate fails, emit
+ `report_incomplete` and stop; do not file a new issue.
+ - Even though `create_issue` appears in the safe-outputs allowlist
+ (so report mode can use it), calling it from autofix is a contract
+ violation tracked by issue #30. The wrong-tool behaviour is visible
+ in the run log and treated as a defect.
+
+If you find yourself about to call `create_issue` while `inputs.mode ==
+autofix`, stop and re-read this section.
+
## Scope
Every `*.toml` file under the repo root, excluding:
@@ -117,7 +152,9 @@ Apply dedup before emitting.
- `taplo fmt --check` — must exit 0.
- `taplo lint` — must exit 0. If lint fails, do not open the PR
(taplo lint has no `--fix` mode for schema violations).
-3. Open one PR via `create-pull-request`:
+3. Open one PR via `create-pull-request` (safe-output tool
+ `create_pull_request`). **Do not call `create_issue` in this mode** —
+ see the contract above.
- Title: `[lint:toml] auto-applied taplo fmt`.
- Body: summary of files touched, `Closes #` if applicable.
- Labels: `agent:lint:toml`, `agent:autofix`.
@@ -135,3 +172,5 @@ Apply dedup before emitting.
- Do not modify `Cargo.toml` or any TOML inside a Rust workspace.
- Do not auto-apply schema-violation fixes; only formatter changes.
- Do not open more than one PR or issue per run.
+- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`.
+- Do not call `create_pull_request` when `inputs.mode == report`.