From 80d0e9c2633a0563f22afdae9dfa13ddb1120c94 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 03:57:06 +0000 Subject: [PATCH 1/8] Initial plan From 27a78c745e4a821da9ffdc815c092b36bbcbba62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 04:03:52 +0000 Subject: [PATCH 2/8] fix: replace string allocation with bytes.Equal in virtual_fs.go Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../daily-elixir-credo-snippet-audit.lock.yml | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml b/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml index dd8b212e3e1..3b3a0b65401 100644 --- a/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml +++ b/.github/workflows/daily-elixir-credo-snippet-audit.lock.yml @@ -1,5 +1,5 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6cf2dc5cf3f6169a8fc2ae8262681f558655296a60ef8bc1a96550c0f7b3520f","body_hash":"2f1463b9044ab5b109d77da64d2959d9e4394f83c383cfd35a015e481cef9acb","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.201"}} -# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.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":"erlef/setup-beam","sha":"fc68ffb90438ef2936bbb3251622353b3dcb2f93","version":"v1.24.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.27","digest":"sha256:bb5a0150dcff1cddf9b8045bb411b7759806bace0abcb132fb22158073e155d9","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.27@sha256:bb5a0150dcff1cddf9b8045bb411b7759806bace0abcb132fb22158073e155d9"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.27","digest":"sha256:01e58c4383fa9952abe76e0a134a27c970f81f744d6b7861fc9e08b7964d94c3","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.27@sha256:01e58c4383fa9952abe76e0a134a27c970f81f744d6b7861fc9e08b7964d94c3"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.27","digest":"sha256:70df326caf73bf5911340dca4620b529a483dd8f42142b0a41d7b9761ab4ab7a","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.27@sha256:70df326caf73bf5911340dca4620b529a483dd8f42142b0a41d7b9761ab4ab7a"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.27","digest":"sha256:92d820df47b2eff75d93a5bec4dc183a3ec55ed7ddb4f25cb0fdda5c3e995409","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.27@sha256:92d820df47b2eff75d93a5bec4dc183a3ec55ed7ddb4f25cb0fdda5c3e995409"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.0","digest":"sha256:9dbdf42842c224a95016df1d2a85a2901e04204c242079343b302a307d2b8031","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.0@sha256:9dbdf42842c224a95016df1d2a85a2901e04204c242079343b302a307d2b8031"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} +# gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.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":"erlef/setup-beam","sha":"fc68ffb90438ef2936bbb3251622353b3dcb2f93","version":"v1.24.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.27","digest":"sha256:bb5a0150dcff1cddf9b8045bb411b7759806bace0abcb132fb22158073e155d9","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.27@sha256:bb5a0150dcff1cddf9b8045bb411b7759806bace0abcb132fb22158073e155d9"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.27","digest":"sha256:01e58c4383fa9952abe76e0a134a27c970f81f744d6b7861fc9e08b7964d94c3","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.27@sha256:01e58c4383fa9952abe76e0a134a27c970f81f744d6b7861fc9e08b7964d94c3"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.27","digest":"sha256:70df326caf73bf5911340dca4620b529a483dd8f42142b0a41d7b9761ab4ab7a","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.27@sha256:70df326caf73bf5911340dca4620b529a483dd8f42142b0a41d7b9761ab4ab7a"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.27","digest":"sha256:92d820df47b2eff75d93a5bec4dc183a3ec55ed7ddb4f25cb0fdda5c3e995409","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.27@sha256:92d820df47b2eff75d93a5bec4dc183a3ec55ed7ddb4f25cb0fdda5c3e995409"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ @@ -55,7 +55,7 @@ # - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.27@sha256:01e58c4383fa9952abe76e0a134a27c970f81f744d6b7861fc9e08b7964d94c3 # - ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.27@sha256:70df326caf73bf5911340dca4620b529a483dd8f42142b0a41d7b9761ab4ab7a # - ghcr.io/github/gh-aw-firewall/squid:0.27.27@sha256:92d820df47b2eff75d93a5bec4dc183a3ec55ed7ddb4f25cb0fdda5c3e995409 -# - ghcr.io/github/gh-aw-mcpg:v0.4.0@sha256:9dbdf42842c224a95016df1d2a85a2901e04204c242079343b302a307d2b8031 +# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b # - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 @@ -542,7 +542,7 @@ jobs: GH_AW_SKILL_DIR: ".claude/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.27@sha256:bb5a0150dcff1cddf9b8045bb411b7759806bace0abcb132fb22158073e155d9 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.27@sha256:01e58c4383fa9952abe76e0a134a27c970f81f744d6b7861fc9e08b7964d94c3 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.27@sha256:70df326caf73bf5911340dca4620b529a483dd8f42142b0a41d7b9761ab4ab7a ghcr.io/github/gh-aw-firewall/squid:0.27.27@sha256:92d820df47b2eff75d93a5bec4dc183a3ec55ed7ddb4f25cb0fdda5c3e995409 ghcr.io/github/gh-aw-mcpg:v0.4.0@sha256:9dbdf42842c224a95016df1d2a85a2901e04204c242079343b302a307d2b8031 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.27@sha256:bb5a0150dcff1cddf9b8045bb411b7759806bace0abcb132fb22158073e155d9 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.27@sha256:01e58c4383fa9952abe76e0a134a27c970f81f744d6b7861fc9e08b7964d94c3 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.27@sha256:70df326caf73bf5911340dca4620b529a483dd8f42142b0a41d7b9761ab4ab7a ghcr.io/github/gh-aw-firewall/squid:0.27.27@sha256:92d820df47b2eff75d93a5bec4dc183a3ec55ed7ddb4f25cb0fdda5c3e995409 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -719,7 +719,7 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.0' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) cat << GH_AW_MCP_CONFIG_7cbd56920d45855d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" @@ -799,7 +799,7 @@ jobs: GITHUB_COPILOT_BASE_URL: ${{ env.GITHUB_COPILOT_BASE_URL }} GH_AW_NETWORK_ISOLATION: 'true' CLI_PROXY_POLICY: '{"allow-only":{"repos":"all","min-integrity":"none"}}' - CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.4.0' + CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.4.1' run: | bash "${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh" - name: Execute Claude Code CLI @@ -999,6 +999,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/agent-stdio.log + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -1020,15 +1021,7 @@ jobs: continue-on-error: true env: AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs - run: | - # Best-effort permission fix for artifact upload (AWF cleanup may not have run) - sudo -n chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true - # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) - if command -v awf &> /dev/null; then - awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" - else - echo 'AWF binary not installed, skipping firewall log summary' - fi + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_firewall_logs.sh" --rootless - name: Parse token usage for step summary if: always() continue-on-error: true From 5cfec5cada52e29fc033ff10c315af8878dc2035 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 04:07:02 +0000 Subject: [PATCH 3/8] fix: replace string allocation with bytes.Equal in virtual_fs.go Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/virtual_fs.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/parser/virtual_fs.go b/pkg/parser/virtual_fs.go index 99fe8926809..7509efde39b 100644 --- a/pkg/parser/virtual_fs.go +++ b/pkg/parser/virtual_fs.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "fmt" "os" "strings" @@ -34,7 +35,7 @@ func RegisterBuiltinVirtualFile(path string, content []byte) { builtinVirtualFiles = make(map[string][]byte) } if existing, ok := builtinVirtualFiles[path]; ok { - if string(existing) != string(content) { + if !bytes.Equal(existing, content) { panic(fmt.Sprintf("RegisterBuiltinVirtualFile: path %q already registered with different content", path)) } return // idempotent: same content, no-op From 78ff72e6c4c5461fd2e5135b853751ca887fd154 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 04:25:20 +0000 Subject: [PATCH 4/8] fix: resolve all non-largefunc custom linter findings - bytes.Equal: replace string(a) != string(b) with !bytes.Equal(a, b) - map[string]struct{}: convert map[string]bool set usage to map[string]struct{} (action_resolver, lsp_manager, runner_topology_validation) - slices.SortFunc: replace sort.Slice with type-safe slices.SortFunc - redundant .Error(): remove .Error() calls in fmt.Sprintf %s format strings - json.Unmarshal: handle discarded error returns in test files - len(s) > 0: replace with s != "" across 50+ locations - len(strings.Split(...)): replace with strings.Count(...)+1 - time.Sleep: replace with context-aware select in mcp_inspect.go - io.WriteString: replace h.Write([]byte(s)) with io.WriteString(h, s) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/actions_build_command.go | 2 +- pkg/cli/audit_report.go | 2 +- pkg/cli/codemod_agent_session.go | 2 +- pkg/cli/codemod_assign_to_agent.go | 4 ++-- pkg/cli/codemod_bash_anonymous.go | 2 +- pkg/cli/codemod_cli_proxy_mode.go | 4 ++-- pkg/cli/codemod_difc_proxy.go | 4 ++-- pkg/cli/codemod_discussion_flag.go | 4 ++-- pkg/cli/codemod_engine_env_secrets.go | 6 ++--- pkg/cli/codemod_engine_steps.go | 2 +- .../codemod_engine_to_top_level_helpers.go | 2 +- pkg/cli/codemod_expires_integer.go | 2 +- pkg/cli/codemod_mcp_mode_to_type.go | 2 +- pkg/cli/codemod_mcp_network.go | 8 +++---- pkg/cli/codemod_network_firewall.go | 2 +- pkg/cli/codemod_permissions_write.go | 2 +- pkg/cli/codemod_playwright_domains.go | 4 ++-- ...emod_pull_request_target_checkout_false.go | 2 +- pkg/cli/codemod_run_install_scripts.go | 2 +- pkg/cli/codemod_serena_import.go | 2 +- pkg/cli/codemod_slash_command.go | 2 +- pkg/cli/codemod_steps_run_secrets_env.go | 3 ++- pkg/cli/codemod_upload_assets.go | 2 +- pkg/cli/codemod_user_rate_limit.go | 2 +- pkg/cli/generate_action_metadata_command.go | 6 ++--- pkg/cli/git.go | 8 +++---- pkg/cli/logs_awinfo_backward_compat_test.go | 8 +++++-- pkg/cli/mcp_inspect.go | 7 ++++-- pkg/cli/poutine.go | 4 ++-- pkg/cli/remove_command.go | 6 ++--- pkg/cli/runner_guard.go | 2 +- pkg/cli/workflows.go | 2 +- pkg/cli/yaml_frontmatter_utils.go | 2 +- pkg/logger/logger.go | 3 ++- pkg/parser/schedule_fuzzy_scatter.go | 3 ++- pkg/workflow/action_cache.go | 4 ++-- pkg/workflow/action_cache_test.go | 22 ++++++++--------- pkg/workflow/action_resolver.go | 24 +++++++++---------- pkg/workflow/action_resolver_test.go | 12 +++++----- pkg/workflow/claude_tools.go | 2 +- pkg/workflow/codex_logs.go | 2 +- pkg/workflow/compiler_safe_outputs_job.go | 2 +- pkg/workflow/dependabot_test.go | 4 +++- pkg/workflow/frontmatter_error.go | 2 +- pkg/workflow/js.go | 2 +- pkg/workflow/lsp_manager.go | 6 ++--- pkg/workflow/maintenance_cron.go | 3 ++- pkg/workflow/model_alias_validation.go | 4 ++-- pkg/workflow/pip.go | 2 +- pkg/workflow/runner_topology_validation.go | 10 ++++---- pkg/workflow/safe_jobs.go | 4 ++-- pkg/workflow/safe_outputs_messages_config.go | 4 ++-- pkg/workflow/shell.go | 2 +- pkg/workflow/strings.go | 5 ++-- pkg/workflow/xml_comments.go | 2 +- 55 files changed, 126 insertions(+), 112 deletions(-) diff --git a/pkg/cli/actions_build_command.go b/pkg/cli/actions_build_command.go index ae7bea2bae6..4c8f193eec7 100644 --- a/pkg/cli/actions_build_command.go +++ b/pkg/cli/actions_build_command.go @@ -76,7 +76,7 @@ func ActionsValidateCommand() error { for _, actionName := range actionDirs { actionPath := filepath.Join(actionsDir, actionName) if err := validateActionYml(actionPath); err != nil { - fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("✗ %s/action.yml: %s", actionName, err.Error()))) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("✗ %s/action.yml: %s", actionName, err))) allValid = false } else { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ %s/action.yml is valid", actionName))) diff --git a/pkg/cli/audit_report.go b/pkg/cli/audit_report.go index ed14d79e7e5..1d198a2120b 100644 --- a/pkg/cli/audit_report.go +++ b/pkg/cli/audit_report.go @@ -838,7 +838,7 @@ func stripGHALogTimestamps(content string) string { if zPos+1 <= len(line) { line = line[zPos+1:] // Skip leading space after the timestamp - if len(line) > 0 && line[0] == ' ' { + if line != "" && line[0] == ' ' { line = line[1:] } } diff --git a/pkg/cli/codemod_agent_session.go b/pkg/cli/codemod_agent_session.go index e451a753afb..23ffddfe553 100644 --- a/pkg/cli/codemod_agent_session.go +++ b/pkg/cli/codemod_agent_session.go @@ -57,7 +57,7 @@ func getAgentTaskToAgentSessionCodemod() Codemod { } // Check if we've left the safe-outputs block - if inSafeOutputsBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inSafeOutputsBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, safeOutputsIndent) { inSafeOutputsBlock = false } diff --git a/pkg/cli/codemod_assign_to_agent.go b/pkg/cli/codemod_assign_to_agent.go index 7633b891aec..69427760c0a 100644 --- a/pkg/cli/codemod_assign_to_agent.go +++ b/pkg/cli/codemod_assign_to_agent.go @@ -70,7 +70,7 @@ func getAssignToAgentDefaultAgentCodemod() Codemod { } // Check if we've left the safe-outputs block - if inSafeOutputsBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inSafeOutputsBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, safeOutputsIndent) { inSafeOutputsBlock = false inAssignToAgentBlock = false @@ -86,7 +86,7 @@ func getAssignToAgentDefaultAgentCodemod() Codemod { } // Check if we've left the assign-to-agent block - if inAssignToAgentBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inAssignToAgentBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, assignToAgentIndent) { inAssignToAgentBlock = false } diff --git a/pkg/cli/codemod_bash_anonymous.go b/pkg/cli/codemod_bash_anonymous.go index 03ceab47104..1dce1cf502e 100644 --- a/pkg/cli/codemod_bash_anonymous.go +++ b/pkg/cli/codemod_bash_anonymous.go @@ -66,7 +66,7 @@ func replaceBashAnonymousWithTrue(lines []string) ([]string, bool) { } // Check if we've left the tools block - if inToolsBlock && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if inToolsBlock && trimmed != "" && !strings.HasPrefix(trimmed, "#") { if hasExitedBlock(line, toolsIndent) { inToolsBlock = false } diff --git a/pkg/cli/codemod_cli_proxy_mode.go b/pkg/cli/codemod_cli_proxy_mode.go index 937ff95315c..bc4edf2dd43 100644 --- a/pkg/cli/codemod_cli_proxy_mode.go +++ b/pkg/cli/codemod_cli_proxy_mode.go @@ -99,7 +99,7 @@ func addGitHubModeGhProxyToTools(lines []string) []string { toolsEnd := len(lines) for i := toolsLine + 1; i < len(lines); i++ { trimmed := strings.TrimSpace(lines[i]) - if len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") && hasExitedBlock(lines[i], toolsIndent) { + if trimmed != "" && !strings.HasPrefix(trimmed, "#") && hasExitedBlock(lines[i], toolsIndent) { toolsEnd = i break } @@ -128,7 +128,7 @@ func addGitHubModeGhProxyToTools(lines []string) []string { insertAt := githubLine + 1 for i := githubLine + 1; i < len(lines); i++ { trimmed := strings.TrimSpace(lines[i]) - if len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if trimmed != "" && !strings.HasPrefix(trimmed, "#") { if hasExitedBlock(lines[i], githubIndent) { insertAt = i } else { diff --git a/pkg/cli/codemod_difc_proxy.go b/pkg/cli/codemod_difc_proxy.go index 4a5d3f5a627..64c6e815266 100644 --- a/pkg/cli/codemod_difc_proxy.go +++ b/pkg/cli/codemod_difc_proxy.go @@ -128,7 +128,7 @@ func addIntegrityProxyFalseToToolsGitHub(lines []string) []string { } // Check if we've left the tools block - if inTools && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if inTools && trimmed != "" && !strings.HasPrefix(trimmed, "#") { if hasExitedBlock(line, toolsIndent) { inTools = false inGitHub = false @@ -144,7 +144,7 @@ func addIntegrityProxyFalseToToolsGitHub(lines []string) []string { } // Inside github block: inject integrity-proxy: false before the first sub-field - if inGitHub && !fieldInserted && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if inGitHub && !fieldInserted && trimmed != "" && !strings.HasPrefix(trimmed, "#") { if hasExitedBlock(line, githubIndent) { // Exited github block without seeing any sub-fields; use default indentation fieldIndent := githubIndent + " " diff --git a/pkg/cli/codemod_discussion_flag.go b/pkg/cli/codemod_discussion_flag.go index fa31b1b49d0..b62afc57faf 100644 --- a/pkg/cli/codemod_discussion_flag.go +++ b/pkg/cli/codemod_discussion_flag.go @@ -65,7 +65,7 @@ func getDiscussionFlagRemovalCodemod() Codemod { } // Check if we've left the safe-outputs block - if inSafeOutputsBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inSafeOutputsBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, safeOutputsIndent) { inSafeOutputsBlock = false inAddCommentBlock = false @@ -81,7 +81,7 @@ func getDiscussionFlagRemovalCodemod() Codemod { } // Check if we've left the add-comment block - if inAddCommentBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inAddCommentBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, addCommentIndent) { inAddCommentBlock = false } diff --git a/pkg/cli/codemod_engine_env_secrets.go b/pkg/cli/codemod_engine_env_secrets.go index e18463de0e9..9247a2b9bdb 100644 --- a/pkg/cli/codemod_engine_env_secrets.go +++ b/pkg/cli/codemod_engine_env_secrets.go @@ -175,7 +175,7 @@ func removeUnsafeEngineEnvKeys(lines []string, unsafeKeys map[string]struct { continue } - if inEngine && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") && len(indent) <= len(engineIndent) { + if inEngine && trimmed != "" && !strings.HasPrefix(trimmed, "#") && len(indent) <= len(engineIndent) { inEngine = false inEnv = false removingKey = false @@ -189,7 +189,7 @@ func removeUnsafeEngineEnvKeys(lines []string, unsafeKeys map[string]struct { continue } - if inEnv && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") && len(indent) <= len(envIndent) { + if inEnv && trimmed != "" && !strings.HasPrefix(trimmed, "#") && len(indent) <= len(envIndent) { inEnv = false removingKey = false } @@ -207,7 +207,7 @@ func removeUnsafeEngineEnvKeys(lines []string, unsafeKeys map[string]struct { removingKey = false } - if inEnv && !removingKey && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") && len(indent) > len(envIndent) { + if inEnv && !removingKey && trimmed != "" && !strings.HasPrefix(trimmed, "#") && len(indent) > len(envIndent) { key := parseYAMLMapKey(trimmed) if key != "" && setutil.Contains(unsafeKeys, key) { modified = true diff --git a/pkg/cli/codemod_engine_steps.go b/pkg/cli/codemod_engine_steps.go index 72b145c815a..bc5ac73c9b0 100644 --- a/pkg/cli/codemod_engine_steps.go +++ b/pkg/cli/codemod_engine_steps.go @@ -60,7 +60,7 @@ func getEngineStepsToTopLevelCodemod() Codemod { } // Check if we've exited the engine block - if inEngineBlock && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if inEngineBlock && trimmed != "" && !strings.HasPrefix(trimmed, "#") { lineIndent := getIndentation(line) if len(lineIndent) <= len(engineIndent) { inEngineBlock = false diff --git a/pkg/cli/codemod_engine_to_top_level_helpers.go b/pkg/cli/codemod_engine_to_top_level_helpers.go index 602d45ace2f..2ba6c2d7472 100644 --- a/pkg/cli/codemod_engine_to_top_level_helpers.go +++ b/pkg/cli/codemod_engine_to_top_level_helpers.go @@ -67,7 +67,7 @@ func migrateEngineFieldToTopLevel( engineIndent = getIndentation(line) continue } - if inEngineBlock && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") && len(getIndentation(line)) <= len(engineIndent) { + if inEngineBlock && trimmed != "" && !strings.HasPrefix(trimmed, "#") && len(getIndentation(line)) <= len(engineIndent) { inEngineBlock = false } if inEngineBlock && strings.HasPrefix(trimmed, engineFieldPrefix) { diff --git a/pkg/cli/codemod_expires_integer.go b/pkg/cli/codemod_expires_integer.go index bc0a0ff3693..fc73616f3e8 100644 --- a/pkg/cli/codemod_expires_integer.go +++ b/pkg/cli/codemod_expires_integer.go @@ -81,7 +81,7 @@ func convertExpiresIntegersToDayStrings(lines []string) ([]string, bool) { } // Check if we've left the safe-outputs block - if inSafeOutputsBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inSafeOutputsBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, safeOutputsIndent) { inSafeOutputsBlock = false } diff --git a/pkg/cli/codemod_mcp_mode_to_type.go b/pkg/cli/codemod_mcp_mode_to_type.go index c23a17164e9..673f111f143 100644 --- a/pkg/cli/codemod_mcp_mode_to_type.go +++ b/pkg/cli/codemod_mcp_mode_to_type.go @@ -72,7 +72,7 @@ func renameModeToTypeInMCPServers(lines []string) ([]string, bool) { } // Check if we've left mcp-servers block - if inMCPServers && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inMCPServers && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, mcpServersIndent) { inMCPServers = false } diff --git a/pkg/cli/codemod_mcp_network.go b/pkg/cli/codemod_mcp_network.go index a487eef6185..ba1423f7a83 100644 --- a/pkg/cli/codemod_mcp_network.go +++ b/pkg/cli/codemod_mcp_network.go @@ -171,7 +171,7 @@ func removeFieldFromMCPServer(lines []string, serverName string, fieldName strin } // Check if we've left mcp-servers block - if inMCPServers && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inMCPServers && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, mcpServersIndent) { inMCPServers = false inServerBlock = false @@ -187,7 +187,7 @@ func removeFieldFromMCPServer(lines []string, serverName string, fieldName strin } // Check if we've left the server block - if inServerBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inServerBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { currentIndent := getIndentation(line) // Exit if we're back at mcp-servers level or less if len(currentIndent) <= len(serverIndent) && strings.Contains(line, ":") { @@ -306,7 +306,7 @@ func updateNetworkAllowed(lines []string, domains []string) []string { } // Check if we've left network block - if inNetworkBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inNetworkBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, networkIndent) { inNetworkBlock = false inAllowedBlock = false @@ -376,7 +376,7 @@ func addAllowedToNetwork(lines []string, domains []string) []string { networkIndent = getIndentation(line) } - if inNetworkBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inNetworkBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, networkIndent) { // Found the end of network block insertIndex = i diff --git a/pkg/cli/codemod_network_firewall.go b/pkg/cli/codemod_network_firewall.go index 395c109d0c5..d587f03d87a 100644 --- a/pkg/cli/codemod_network_firewall.go +++ b/pkg/cli/codemod_network_firewall.go @@ -135,7 +135,7 @@ func insertSandboxAfterNetworkBlock(lines []string, sandboxLines []string) []str inNetworkBlock = true continue } - if inNetworkBlock && len(trimmed) > 0 && isTopLevelKey(line) { + if inNetworkBlock && trimmed != "" && isTopLevelKey(line) { insertIndex = i break } diff --git a/pkg/cli/codemod_permissions_write.go b/pkg/cli/codemod_permissions_write.go index 3cd4fe419b3..3ebd0161ed3 100644 --- a/pkg/cli/codemod_permissions_write.go +++ b/pkg/cli/codemod_permissions_write.go @@ -90,7 +90,7 @@ func getMigrateWritePermissionsToReadCodemod() Codemod { } // Check if we've left the permissions block - if inPermissionsBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inPermissionsBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, permissionsIndent) { inPermissionsBlock = false } diff --git a/pkg/cli/codemod_playwright_domains.go b/pkg/cli/codemod_playwright_domains.go index aeca0e99218..7ab2b62b56a 100644 --- a/pkg/cli/codemod_playwright_domains.go +++ b/pkg/cli/codemod_playwright_domains.go @@ -127,7 +127,7 @@ func removeFieldFromPlaywright(lines []string, fieldName string) ([]string, bool } // Check if we've left the tools block - if inTools && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if inTools && trimmed != "" && !strings.HasPrefix(trimmed, "#") { if hasExitedBlock(line, toolsIndent) { inTools = false inPlaywright = false @@ -143,7 +143,7 @@ func removeFieldFromPlaywright(lines []string, fieldName string) ([]string, bool } // Check if we've left the playwright block - if inPlaywright && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if inPlaywright && trimmed != "" && !strings.HasPrefix(trimmed, "#") { if hasExitedBlock(line, playwrightIndent) { inPlaywright = false } diff --git a/pkg/cli/codemod_pull_request_target_checkout_false.go b/pkg/cli/codemod_pull_request_target_checkout_false.go index 1cbe167964a..93a532fa273 100644 --- a/pkg/cli/codemod_pull_request_target_checkout_false.go +++ b/pkg/cli/codemod_pull_request_target_checkout_false.go @@ -141,7 +141,7 @@ func ensureCheckoutFalseForPullRequestTarget(lines []string) ([]string, bool) { if strings.TrimSpace(next) == "" { continue } - if len(next) > 0 && (next[0] == ' ' || next[0] == '\t') { + if next != "" && (next[0] == ' ' || next[0] == '\t') { return lines, false } break diff --git a/pkg/cli/codemod_run_install_scripts.go b/pkg/cli/codemod_run_install_scripts.go index 7d26765e0aa..2b57c47088e 100644 --- a/pkg/cli/codemod_run_install_scripts.go +++ b/pkg/cli/codemod_run_install_scripts.go @@ -171,7 +171,7 @@ func detectFrontmatterIndent(lines []string) string { continue } ind := getIndentation(line) - if len(ind) > 0 { + if ind != "" { return ind } } diff --git a/pkg/cli/codemod_serena_import.go b/pkg/cli/codemod_serena_import.go index 7fda9010933..d2dc10d0c85 100644 --- a/pkg/cli/codemod_serena_import.go +++ b/pkg/cli/codemod_serena_import.go @@ -190,7 +190,7 @@ func removeSerenaFromToolsList(lines []string) ([]string, bool) { } // Track block exit. - if inToolsBlock && len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") { + if inToolsBlock && trimmed != "" && !strings.HasPrefix(trimmed, "#") { if hasExitedBlock(line, toolsIndent) { inToolsBlock = false } diff --git a/pkg/cli/codemod_slash_command.go b/pkg/cli/codemod_slash_command.go index 99db468e3f5..0e3581237e6 100644 --- a/pkg/cli/codemod_slash_command.go +++ b/pkg/cli/codemod_slash_command.go @@ -50,7 +50,7 @@ func getCommandToSlashCommandCodemod() Codemod { } // Check if we've left the on block - if inOnBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inOnBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, onIndent) { inOnBlock = false } diff --git a/pkg/cli/codemod_steps_run_secrets_env.go b/pkg/cli/codemod_steps_run_secrets_env.go index 05ba1073055..4ab842a01f2 100644 --- a/pkg/cli/codemod_steps_run_secrets_env.go +++ b/pkg/cli/codemod_steps_run_secrets_env.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "hash/fnv" + "io" "regexp" "strings" @@ -446,7 +447,7 @@ func mapRunExpressionToEnvBinding(body string) (string, string, bool) { func hashedBindingName(prefix, body string) string { h := fnv.New32a() // fnv.Hash.Write on in-memory bytes is guaranteed not to return an error. - _, _ = h.Write([]byte(body)) + _, _ = io.WriteString(h, body) return fmt.Sprintf("%s_%08x", prefix, h.Sum32()) } diff --git a/pkg/cli/codemod_upload_assets.go b/pkg/cli/codemod_upload_assets.go index 0909bb3d940..1d8922de291 100644 --- a/pkg/cli/codemod_upload_assets.go +++ b/pkg/cli/codemod_upload_assets.go @@ -50,7 +50,7 @@ func getUploadAssetsCodemod() Codemod { } // Check if we've left the safe-outputs block - if inSafeOutputsBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inSafeOutputsBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, safeOutputsIndent) { inSafeOutputsBlock = false } diff --git a/pkg/cli/codemod_user_rate_limit.go b/pkg/cli/codemod_user_rate_limit.go index 59321254de5..2afbcc9f93f 100644 --- a/pkg/cli/codemod_user_rate_limit.go +++ b/pkg/cli/codemod_user_rate_limit.go @@ -81,7 +81,7 @@ func renameRateLimitToUserRateLimit(lines []string) ([]string, bool) { if inUserRateLimit { lineIndent := getIndentation(line) if isDescendant(lineIndent, userRateLimitIndent) { - if len(trimmed) > 0 && !strings.HasPrefix(trimmed, "#") && userRateLimitChildIndent == "" { + if trimmed != "" && !strings.HasPrefix(trimmed, "#") && userRateLimitChildIndent == "" { userRateLimitChildIndent = lineIndent } if userRateLimitChildIndent != "" && lineIndent != userRateLimitChildIndent { diff --git a/pkg/cli/generate_action_metadata_command.go b/pkg/cli/generate_action_metadata_command.go index 776992072c1..c60b1e26c2f 100644 --- a/pkg/cli/generate_action_metadata_command.go +++ b/pkg/cli/generate_action_metadata_command.go @@ -84,7 +84,7 @@ func GenerateActionMetadataCommand() error { contentBytes, err := os.ReadFile(jsPath) if err != nil { generateActionMetadataLog.Printf("Skipping %s: failed to read file: %v", filename, err) - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("⚠ Skipping %s: %s", filename, err.Error()))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("⚠ Skipping %s: %s", filename, err))) continue } content := string(contentBytes) @@ -94,7 +94,7 @@ func GenerateActionMetadataCommand() error { // Extract metadata metadata, err := extractActionMetadata(filename, content) if err != nil { - fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("✗ Failed to extract metadata from %s: %s", filename, err.Error()))) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("✗ Failed to extract metadata from %s: %s", filename, err))) continue } @@ -205,7 +205,7 @@ func generateHumanReadableName(actionName string) string { // Replace underscores with spaces and capitalize words words := strings.Split(actionName, "_") for i, word := range words { - if len(word) > 0 { + if word != "" { words[i] = strings.ToUpper(word[:1]) + word[1:] } } diff --git a/pkg/cli/git.go b/pkg/cli/git.go index c6cc712d252..ef6b4adbce2 100644 --- a/pkg/cli/git.go +++ b/pkg/cli/git.go @@ -532,7 +532,7 @@ func hasPendingChanges() (bool, error) { if err != nil { return false, fmt.Errorf("failed to check git status: %w", err) } - return len(strings.TrimSpace(string(output))) > 0, nil + return strings.TrimSpace(string(output)) != "", nil } // checkCleanWorkingDirectory checks if there are uncommitted changes @@ -545,7 +545,7 @@ func checkCleanWorkingDirectory(verbose bool) error { return fmt.Errorf("failed to check git status: %w", err) } - if len(strings.TrimSpace(string(output))) > 0 { + if strings.TrimSpace(string(output)) != "" { return errors.New("working directory has uncommitted changes, please commit or stash them first") } @@ -603,7 +603,7 @@ func checkWorkflowFileStatus(workflowPath string) (*WorkflowFileStatus, error) { } statusOutput := string(output) // Don't trim - the leading space is significant! - if len(statusOutput) > 0 { + if statusOutput != "" { gitLog.Printf("Git status output: %q", statusOutput) // Parse the status line (format: XY filename) // X = index (staged) status, Y = working tree (unstaged) status @@ -647,7 +647,7 @@ func checkWorkflowFileStatus(workflowPath string) (*WorkflowFileStatus, error) { return status, nil // Ignore error, return current status } - if len(strings.TrimSpace(string(output))) > 0 { + if strings.TrimSpace(string(output)) != "" { status.HasUnpushedCommits = true gitLog.Print("File has unpushed commits") } diff --git a/pkg/cli/logs_awinfo_backward_compat_test.go b/pkg/cli/logs_awinfo_backward_compat_test.go index 5aaaa055b95..6d7691be4da 100644 --- a/pkg/cli/logs_awinfo_backward_compat_test.go +++ b/pkg/cli/logs_awinfo_backward_compat_test.go @@ -170,7 +170,9 @@ func TestAwInfoMarshaling(t *testing.T) { if tt.shouldContainNew { // Check for awf_version in JSON var temp map[string]any - json.Unmarshal(data, &temp) + if err := json.Unmarshal(data, &temp); err != nil { + t.Fatalf("%s: failed to unmarshal JSON: %v", tt.description, err) + } if _, exists := temp["awf_version"]; !exists { t.Errorf("%s: JSON should contain awf_version field, got: %s", tt.description, jsonStr) } @@ -179,7 +181,9 @@ func TestAwInfoMarshaling(t *testing.T) { if tt.shouldContainOld { // Check for firewall_version in JSON var temp map[string]any - json.Unmarshal(data, &temp) + if err := json.Unmarshal(data, &temp); err != nil { + t.Fatalf("%s: failed to unmarshal JSON: %v", tt.description, err) + } if _, exists := temp["firewall_version"]; !exists { t.Errorf("%s: JSON should contain firewall_version field, got: %s", tt.description, jsonStr) } diff --git a/pkg/cli/mcp_inspect.go b/pkg/cli/mcp_inspect.go index 51de62ad520..2f2cf566d5f 100644 --- a/pkg/cli/mcp_inspect.go +++ b/pkg/cli/mcp_inspect.go @@ -134,8 +134,11 @@ func InspectWorkflowMCP(ctx context.Context, workflowFile string, serverFilter s if err := mcpScriptsServerCmd.Process.Signal(os.Interrupt); err != nil && verbose { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to send interrupt signal: %v", err))) } - // Wait a moment for graceful shutdown - time.Sleep(mcpScriptsServerShutdownDelay) + // Wait a moment for graceful shutdown (respects context cancellation) + select { + case <-time.After(mcpScriptsServerShutdownDelay): + case <-ctx.Done(): + } // Attempt force kill (may fail if process already exited gracefully, which is fine) _ = mcpScriptsServerCmd.Process.Kill() } diff --git a/pkg/cli/poutine.go b/pkg/cli/poutine.go index 2fab3879650..5226b52c80a 100644 --- a/pkg/cli/poutine.go +++ b/pkg/cli/poutine.go @@ -282,7 +282,7 @@ func parseAndDisplayPoutineOutput(stdout, targetFile string, verbose bool) (int, trimmed := strings.TrimSpace(stdout) if !strings.HasPrefix(trimmed, "{") { // Non-JSON output, likely an error - if len(trimmed) > 0 { + if trimmed != "" { return 0, fmt.Errorf("unexpected poutine output format: %s", trimmed) } return 0, nil @@ -392,7 +392,7 @@ func parseAndDisplayPoutineOutputForDirectory(stdout string, verbose bool, gitRo trimmed := strings.TrimSpace(stdout) if !strings.HasPrefix(trimmed, "{") { // Non-JSON output, likely an error - if len(trimmed) > 0 { + if trimmed != "" { return 0, fmt.Errorf("unexpected poutine output format: %s", trimmed) } return 0, nil diff --git a/pkg/cli/remove_command.go b/pkg/cli/remove_command.go index 61bea65c8e6..634e3a98082 100644 --- a/pkg/cli/remove_command.go +++ b/pkg/cli/remove_command.go @@ -447,13 +447,13 @@ func parseIncludePath(line string) string { case strings.HasPrefix(trimmed, "{{#import"): rest = trimmed[len("{{#import"):] // Skip optional marker '?' - if len(rest) > 0 && rest[0] == '?' { + if rest != "" && rest[0] == '?' { rest = rest[1:] } // Skip optional whitespace, then an optional single colon, then optional whitespace // (mirrors the regex \s*:?\s* in IncludeDirectivePattern) rest = strings.TrimSpace(rest) - if len(rest) > 0 && rest[0] == ':' { + if rest != "" && rest[0] == ':' { rest = strings.TrimSpace(rest[1:]) } // Extract path up to closing "}}" and require only whitespace after it. @@ -475,7 +475,7 @@ func parseIncludePath(line string) string { } // Handle @include and @import: skip optional marker '?' - if len(rest) > 0 && rest[0] == '?' { + if rest != "" && rest[0] == '?' { rest = rest[1:] } // Require at least one whitespace character after the directive keyword diff --git a/pkg/cli/runner_guard.go b/pkg/cli/runner_guard.go index c8f3895ec92..879e16a40c3 100644 --- a/pkg/cli/runner_guard.go +++ b/pkg/cli/runner_guard.go @@ -149,7 +149,7 @@ func parseAndDisplayRunnerGuardOutput(stdout string, verbose bool, gitRoot strin trimmed := strings.TrimSpace(stdout) if !strings.HasPrefix(trimmed, "{") && !strings.HasPrefix(trimmed, "[") { - if len(trimmed) > 0 { + if trimmed != "" { return 0, fmt.Errorf("unexpected runner-guard output format: %s", trimmed) } return 0, nil diff --git a/pkg/cli/workflows.go b/pkg/cli/workflows.go index c3b0288a3a0..ed9de7da3c5 100644 --- a/pkg/cli/workflows.go +++ b/pkg/cli/workflows.go @@ -431,7 +431,7 @@ func extractWorkflowNameFromFile(filePath string) (title string, err error) { // Capitalize first letter of each word words := strings.Fields(baseName) for i, word := range words { - if len(word) > 0 { + if word != "" { words[i] = strings.ToUpper(word[:1]) + word[1:] } } diff --git a/pkg/cli/yaml_frontmatter_utils.go b/pkg/cli/yaml_frontmatter_utils.go index 8673ba0b9f0..c1ffb3bf70c 100644 --- a/pkg/cli/yaml_frontmatter_utils.go +++ b/pkg/cli/yaml_frontmatter_utils.go @@ -188,7 +188,7 @@ func removeFieldFromBlock(lines []string, fieldName string, parentBlock string) } // Check if we've left the parent block - if inParentBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if inParentBlock && trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { if hasExitedBlock(line, parentIndent) { inParentBlock = false } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 7a693752873..1dd22d39172 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -4,6 +4,7 @@ import ( "fmt" "hash/fnv" "image/color" + "io" "os" "strings" "sync" @@ -99,7 +100,7 @@ func selectNamespaceLabel(namespace string) string { // Use FNV-1a hash for consistent color assignment h := fnv.New32a() // hash.Hash.Write never returns an error in practice, but check to satisfy gosec G104 - if _, err := h.Write([]byte(namespace)); err != nil { + if _, err := io.WriteString(h, namespace); err != nil { // Return plain namespace (no color) if write somehow fails return namespace } diff --git a/pkg/parser/schedule_fuzzy_scatter.go b/pkg/parser/schedule_fuzzy_scatter.go index 6471ee1473e..4032b8795ba 100644 --- a/pkg/parser/schedule_fuzzy_scatter.go +++ b/pkg/parser/schedule_fuzzy_scatter.go @@ -3,6 +3,7 @@ package parser import ( "fmt" "hash/fnv" + "io" "strconv" "strings" @@ -172,7 +173,7 @@ func avoidPeakMinutes(hour, minute int) int { func stableHash(s string, modulo int) int { h := fnv.New32a() // hash.Hash.Write never returns an error in practice, but check to satisfy gosec G104 - if _, err := h.Write([]byte(s)); err != nil { + if _, err := io.WriteString(h, s); err != nil { // Return 0 (safe fallback) if write somehow fails scheduleFuzzyScatterLog.Printf("Warning: hash write failed: %v", err) return 0 diff --git a/pkg/workflow/action_cache.go b/pkg/workflow/action_cache.go index f82a8a53234..56aa0264ee5 100644 --- a/pkg/workflow/action_cache.go +++ b/pkg/workflow/action_cache.go @@ -104,7 +104,7 @@ func (c *ActionCache) DeleteContainerPin(image string) { // This is used to keep actions-lock.json a faithful reflection of what the // compiled workflows actually reference — entries for old action versions that // are no longer used by any workflow are removed. -func (c *ActionCache) PruneOrphanedEntries(referencedKeys map[string]bool) int { +func (c *ActionCache) PruneOrphanedEntries(referencedKeys map[string]struct{}) int { if len(referencedKeys) == 0 { return 0 } @@ -139,7 +139,7 @@ func (c *ActionCache) PruneOrphanedEntries(referencedKeys map[string]bool) int { pruned := 0 for key := range c.Entries { - if !referencedKeys[key] && !isCompilerGenerated(key) { + if _, referenced := referencedKeys[key]; !referenced && !isCompilerGenerated(key) { delete(c.Entries, key) c.dirty = true pruned++ diff --git a/pkg/workflow/action_cache_test.go b/pkg/workflow/action_cache_test.go index f0a4d8ecec0..87e7863a369 100644 --- a/pkg/workflow/action_cache_test.go +++ b/pkg/workflow/action_cache_test.go @@ -899,9 +899,9 @@ func TestPruneOrphanedEntries(t *testing.T) { } // Only v1.9.1 and checkout are referenced by compiled workflows. - referenced := map[string]bool{ - "microsoft/apm-action@v1.9.1": true, - "actions/checkout@v4": true, + referenced := map[string]struct{}{ + "microsoft/apm-action@v1.9.1": {}, + "actions/checkout@v4": {}, } pruned := cache.PruneOrphanedEntries(referenced) @@ -932,7 +932,7 @@ func TestPruneOrphanedEntries_EmptyReferenced(t *testing.T) { cache.Set("actions/checkout", "v4", "sha1") cache.Set("actions/setup-node", "v4", "sha2") - pruned := cache.PruneOrphanedEntries(map[string]bool{}) + pruned := cache.PruneOrphanedEntries(map[string]struct{}{}) if pruned != 0 { t.Errorf("Expected 0 pruned entries (empty referenced set is a no-op), got %d", pruned) @@ -951,9 +951,9 @@ func TestPruneOrphanedEntries_NoneOrphaned(t *testing.T) { cache.Set("actions/checkout", "v4", "sha1") cache.Set("actions/setup-node", "v4", "sha2") - referenced := map[string]bool{ - "actions/checkout@v4": true, - "actions/setup-node@v4": true, + referenced := map[string]struct{}{ + "actions/checkout@v4": {}, + "actions/setup-node@v4": {}, } pruned := cache.PruneOrphanedEntries(referenced) @@ -976,8 +976,8 @@ func TestPruneOrphanedEntries_AllOrphaned(t *testing.T) { cache.Set("cli/gh-extension-precompile", "v2.1.0", "sha2") // Referenced set is non-empty but contains neither of the cached entries. - referenced := map[string]bool{ - "some/other-action@v1": true, + referenced := map[string]struct{}{ + "some/other-action@v1": {}, } pruned := cache.PruneOrphanedEntries(referenced) @@ -1010,8 +1010,8 @@ func TestPruneOrphanedEntries_PreservesCompilerGenerated(t *testing.T) { } // Only reference microsoft/apm-action, but not the compiler-generated or runtime-managed ones - referenced := map[string]bool{ - "microsoft/apm-action@v1.7.2": true, + referenced := map[string]struct{}{ + "microsoft/apm-action@v1.7.2": {}, } pruned := cache.PruneOrphanedEntries(referenced) diff --git a/pkg/workflow/action_resolver.go b/pkg/workflow/action_resolver.go index 192d3fdd814..584e1f96176 100644 --- a/pkg/workflow/action_resolver.go +++ b/pkg/workflow/action_resolver.go @@ -18,24 +18,24 @@ var resolverLog = logger.New("workflow:action_resolver") // ActionResolver handles resolving action SHAs using GitHub CLI type ActionResolver struct { cache *ActionCache - failedResolutions map[string]bool // tracks failed resolution attempts in current run (key: "repo@version") - usedCacheKeys map[string]bool // tracks cache keys that were hit or newly set during this run + failedResolutions map[string]struct{} // tracks failed resolution attempts in current run (key: "repo@version") + usedCacheKeys map[string]struct{} // tracks cache keys that were hit or newly set during this run } // NewActionResolver creates a new action resolver func NewActionResolver(cache *ActionCache) *ActionResolver { return &ActionResolver{ cache: cache, - failedResolutions: make(map[string]bool), - usedCacheKeys: make(map[string]bool), + failedResolutions: make(map[string]struct{}), + usedCacheKeys: make(map[string]struct{}), } } // GetUsedCacheKeys returns the set of cache keys (in "repo@version" format) that // were successfully resolved from the cache or written to the cache during this run. // These represent the action pins actually referenced by the compiled workflows. -func (r *ActionResolver) GetUsedCacheKeys() map[string]bool { - keys := make(map[string]bool, len(r.usedCacheKeys)) +func (r *ActionResolver) GetUsedCacheKeys() map[string]struct{} { + keys := make(map[string]struct{}, len(r.usedCacheKeys)) maps.Copy(keys, r.usedCacheKeys) return keys } @@ -43,7 +43,7 @@ func (r *ActionResolver) GetUsedCacheKeys() map[string]bool { // MarkCacheKeyAsUsed explicitly marks a cache key as used during this compilation run. // This is useful for compiler-generated actions that aren't resolved through ResolveSHA. func (r *ActionResolver) MarkCacheKeyAsUsed(cacheKey string) { - r.usedCacheKeys[cacheKey] = true + r.usedCacheKeys[cacheKey] = struct{}{} resolverLog.Printf("Marked cache key as used: %s", cacheKey) } @@ -76,8 +76,8 @@ func (r *ActionResolver) MarkCompilerGeneratedActionsAsUsed() { marked := 0 for _, repo := range compilerGeneratedRepos { if cacheKey, _, found := r.cache.FindAnyEntryForRepo(repo); found { - if !r.usedCacheKeys[cacheKey] { - r.usedCacheKeys[cacheKey] = true + if _, alreadyUsed := r.usedCacheKeys[cacheKey]; !alreadyUsed { + r.usedCacheKeys[cacheKey] = struct{}{} marked++ resolverLog.Printf("Marked compiler-generated action as used: %s", cacheKey) } @@ -96,10 +96,10 @@ func (r *ActionResolver) ResolveSHA(ctx context.Context, repo, version string) ( // Create a cache key for tracking failed resolutions and cache lookups. // Computed once here and reused below to avoid duplicate allocation. cacheKey := formatActionCacheKey(repo, version) - r.usedCacheKeys[cacheKey] = true + r.usedCacheKeys[cacheKey] = struct{}{} // Check if we've already failed to resolve this action in this run - if r.failedResolutions[cacheKey] { + if _, failed := r.failedResolutions[cacheKey]; failed { resolverLog.Printf("Skipping resolution for %s@%s: already failed in this run", repo, version) return "", fmt.Errorf("previously failed to resolve %s@%s in this compilation run", repo, version) } @@ -129,7 +129,7 @@ func (r *ActionResolver) ResolveSHA(ctx context.Context, repo, version string) ( if err != nil { resolverLog.Printf("Failed to resolve %s@%s: %v", repo, version, err) // Mark this resolution as failed for this compilation run - r.failedResolutions[cacheKey] = true + r.failedResolutions[cacheKey] = struct{}{} resolverLog.Printf("Marked %s as failed, will not retry in this run", cacheKey) return "", err } diff --git a/pkg/workflow/action_resolver_test.go b/pkg/workflow/action_resolver_test.go index 1b792ab38e9..0c31e8c2230 100644 --- a/pkg/workflow/action_resolver_test.go +++ b/pkg/workflow/action_resolver_test.go @@ -89,10 +89,10 @@ func TestActionResolverFailedResolutionCache(t *testing.T) { // Verify the failed resolution was tracked cacheKey := formatActionCacheKey(repo, version) - if !resolver.failedResolutions[cacheKey] { + if _, ok := resolver.failedResolutions[cacheKey]; !ok { t.Errorf("Expected failed resolution to be tracked for %s", cacheKey) } - if !resolver.GetUsedCacheKeys()[cacheKey] { + if _, ok := resolver.GetUsedCacheKeys()[cacheKey]; !ok { t.Errorf("Expected used cache keys to track attempted resolution for %s", cacheKey) } @@ -107,7 +107,7 @@ func TestActionResolverFailedResolutionCache(t *testing.T) { if !strings.Contains(err2.Error(), expectedErrMsg) { t.Errorf("Expected error message to contain %q, got: %v", expectedErrMsg, err2) } - if !resolver.GetUsedCacheKeys()[cacheKey] { + if _, ok := resolver.GetUsedCacheKeys()[cacheKey]; !ok { t.Errorf("Expected used cache keys to retain attempted resolution key %s", cacheKey) } } @@ -276,10 +276,10 @@ func TestActionResolverUsedCacheKeysOnCacheHit(t *testing.T) { } usedKeys := resolver.GetUsedCacheKeys() - if !usedKeys["owner/action-a@v1"] { + if _, ok := usedKeys["owner/action-a@v1"]; !ok { t.Error("Expected owner/action-a@v1 to be in used cache keys after a cache hit") } - if usedKeys["owner/action-b@v2"] { + if _, ok := usedKeys["owner/action-b@v2"]; ok { t.Error("Expected owner/action-b@v2 to be absent from used cache keys (never resolved)") } } @@ -297,7 +297,7 @@ func TestActionResolverGetUsedCacheKeysReturnsCopy(t *testing.T) { usedKeys := resolver.GetUsedCacheKeys() delete(usedKeys, "owner/action-a@v1") - if !resolver.GetUsedCacheKeys()["owner/action-a@v1"] { + if _, ok := resolver.GetUsedCacheKeys()["owner/action-a@v1"]; !ok { t.Error("Expected resolver used cache keys to be immutable via returned map") } } diff --git a/pkg/workflow/claude_tools.go b/pkg/workflow/claude_tools.go index 7f00c5620a8..bdfcc56742c 100644 --- a/pkg/workflow/claude_tools.go +++ b/pkg/workflow/claude_tools.go @@ -234,7 +234,7 @@ func hasBashWildcard(commands []any) bool { // isClaudeToolName uses the existing Claude naming convention heuristic: // valid Claude tool keys are expected to start with an uppercase ASCII letter. func isClaudeToolName(toolName string) bool { - return len(toolName) > 0 && toolName[0] >= 'A' && toolName[0] <= 'Z' + return toolName != "" && toolName[0] >= 'A' && toolName[0] <= 'Z' } func appendTopLevelClaudeTools(allowedTools []string, tools map[string]any, cacheMemoryConfig *CacheMemoryConfig) []string { diff --git a/pkg/workflow/codex_logs.go b/pkg/workflow/codex_logs.go index 8798bd9b243..b130bc9fc3e 100644 --- a/pkg/workflow/codex_logs.go +++ b/pkg/workflow/codex_logs.go @@ -14,7 +14,7 @@ var codexLogsLog = logger.New("workflow:codex_logs") // ParseLogMetrics implements engine-specific log parsing for Codex func (e *CodexEngine) ParseLogMetrics(logContent string, verbose bool) LogMetrics { - codexLogsLog.Printf("Parsing Codex log metrics: log_size=%d bytes, lines=%d", len(logContent), len(strings.Split(logContent, "\n"))) + codexLogsLog.Printf("Parsing Codex log metrics: log_size=%d bytes, lines=%d", len(logContent), strings.Count(logContent, "\n")+1) var metrics LogMetrics var totalTokenUsage int diff --git a/pkg/workflow/compiler_safe_outputs_job.go b/pkg/workflow/compiler_safe_outputs_job.go index 8fd947a4602..f3f099b176a 100644 --- a/pkg/workflow/compiler_safe_outputs_job.go +++ b/pkg/workflow/compiler_safe_outputs_job.go @@ -896,7 +896,7 @@ func scriptNameToHandlerName(scriptName string) string { var sb strings.Builder sb.WriteString("handle") for _, part := range parts { - if len(part) > 0 { + if part != "" { sb.WriteString(strings.ToUpper(part[:1]) + part[1:]) } } diff --git a/pkg/workflow/dependabot_test.go b/pkg/workflow/dependabot_test.go index 0c5af851a9a..c844fb5bceb 100644 --- a/pkg/workflow/dependabot_test.go +++ b/pkg/workflow/dependabot_test.go @@ -605,7 +605,9 @@ func TestGenerateDependabotManifests_WithDependencies(t *testing.T) { // Verify package.json content data, _ := os.ReadFile(packageJSONPath) var pkgJSON PackageJSON - json.Unmarshal(data, &pkgJSON) + if err := json.Unmarshal(data, &pkgJSON); err != nil { + t.Fatalf("failed to unmarshal package.json: %v", err) + } if len(pkgJSON.Dependencies) != 1 { t.Errorf("expected 1 dependency, got %d", len(pkgJSON.Dependencies)) diff --git a/pkg/workflow/frontmatter_error.go b/pkg/workflow/frontmatter_error.go index 24cf1403a9a..037129301f1 100644 --- a/pkg/workflow/frontmatter_error.go +++ b/pkg/workflow/frontmatter_error.go @@ -144,7 +144,7 @@ func (c *Compiler) createFrontmatterError(filePath, content string, err error, f // frontmatter start so the IDE navigates to the right file and section rather than // defaulting to line 1, col 1. frontmatterErrorLog.Printf("Using fallback error message: %v", err) - fallbackFmt := fmt.Sprintf("%s:%d:1: error: %s", filePath, frontmatterLineOffset, err.Error()) + fallbackFmt := fmt.Sprintf("%s:%d:1: error: %s", filePath, frontmatterLineOffset, err) return parser.NewFormattedParserError(fallbackFmt) } diff --git a/pkg/workflow/js.go b/pkg/workflow/js.go index dcd9a4e5792..8314d6dc86c 100644 --- a/pkg/workflow/js.go +++ b/pkg/workflow/js.go @@ -85,7 +85,7 @@ func removeJavaScriptComments(code string) string { // Remove the trailing newline we added resultStr := result.String() - if len(resultStr) > 0 && resultStr[len(resultStr)-1] == '\n' { + if resultStr != "" && resultStr[len(resultStr)-1] == '\n' { resultStr = resultStr[:len(resultStr)-1] } diff --git a/pkg/workflow/lsp_manager.go b/pkg/workflow/lsp_manager.go index 944fa08e7b5..f22e21653e4 100644 --- a/pkg/workflow/lsp_manager.go +++ b/pkg/workflow/lsp_manager.go @@ -223,7 +223,7 @@ func (m *LSPManager) RuntimeRequirements() []RuntimeRequirement { return nil } - seen := make(map[string]bool) + seen := make(map[string]struct{}) var result []RuntimeRequirement langs := make([]string, 0, len(m.servers)) @@ -237,10 +237,10 @@ func (m *LSPManager) RuntimeRequirements() []RuntimeRequirement { if !ok || spec.RuntimeID == "" { continue } - if seen[spec.RuntimeID] { + if _, ok := seen[spec.RuntimeID]; ok { continue } - seen[spec.RuntimeID] = true + seen[spec.RuntimeID] = struct{}{} runtime := findRuntimeByID(spec.RuntimeID) if runtime == nil { lspManagerLog.Printf("LSP language %q specifies unknown runtime ID %q; skipping runtime requirement", language, spec.RuntimeID) diff --git a/pkg/workflow/maintenance_cron.go b/pkg/workflow/maintenance_cron.go index e90bd18e8ba..17782fdf367 100644 --- a/pkg/workflow/maintenance_cron.go +++ b/pkg/workflow/maintenance_cron.go @@ -3,6 +3,7 @@ package workflow import ( "fmt" "hash/fnv" + "io" "github.com/github/gh-aw/pkg/logger" ) @@ -42,7 +43,7 @@ func generateMaintenanceCron(minExpiresDays int) (string, string) { // multiple side-repo maintenance workflows so they don't all fire at once. func sideRepoCronSeed(repoSlug string) uint64 { h := fnv.New64a() - _, _ = h.Write([]byte(repoSlug)) + _, _ = io.WriteString(h, repoSlug) return h.Sum64() } diff --git a/pkg/workflow/model_alias_validation.go b/pkg/workflow/model_alias_validation.go index c24649a0a67..1cab53f4993 100644 --- a/pkg/workflow/model_alias_validation.go +++ b/pkg/workflow/model_alias_validation.go @@ -193,12 +193,12 @@ func validateModelIdentifierStrings(identifiers []string, context string) []stri p, err := ParseModelIdentifier(id) if err != nil { // V-MAF-001 / V-MAF-006 - errs = append(errs, fmt.Sprintf("%s: %s", context, err.Error())) + errs = append(errs, fmt.Sprintf("%s: %s", context, err)) continue } // V-MAF-002 and V-MAF-003: validate known parameter values. if err := ValidateKnownParams(p.Params); err != nil { - errs = append(errs, fmt.Sprintf("%s: %s", context, err.Error())) + errs = append(errs, fmt.Sprintf("%s: %s", context, err)) } } return errs diff --git a/pkg/workflow/pip.go b/pkg/workflow/pip.go index 76ef0996ba3..b8384794885 100644 --- a/pkg/workflow/pip.go +++ b/pkg/workflow/pip.go @@ -63,7 +63,7 @@ func extractUvPackages(workflowData *WorkflowData) []string { // extractUvFromCommands extracts uv package names from command strings func extractUvFromCommands(commands string) []string { - pipLog.Printf("Extracting uv packages from commands: line_count=%d", len(strings.Split(commands, "\n"))) + pipLog.Printf("Extracting uv packages from commands: line_count=%d", strings.Count(commands, "\n")+1) var packages []string lines := strings.Split(commands, "\n") diff --git a/pkg/workflow/runner_topology_validation.go b/pkg/workflow/runner_topology_validation.go index 079778ff281..7f237d64b93 100644 --- a/pkg/workflow/runner_topology_validation.go +++ b/pkg/workflow/runner_topology_validation.go @@ -73,7 +73,7 @@ func validateArcDindRootless(workflowData *WorkflowData) error { // Returns a deduplicated list of violation descriptions found. func findRootRequiringPatterns(content string) []string { var violations []string - seen := map[string]bool{} + seen := map[string]struct{}{} for line := range strings.SplitSeq(content, "\n") { trimmed := strings.TrimSpace(line) @@ -86,12 +86,12 @@ func findRootRequiringPatterns(content string) []string { continue } - if containsSudoCommand(trimmed) && !seen["sudo"] { - seen["sudo"] = true + if _, ok := seen["sudo"]; containsSudoCommand(trimmed) && !ok { + seen["sudo"] = struct{}{} violations = append(violations, "sudo") } - if containsAptGetInstall(trimmed) && !seen["apt-get install"] { - seen["apt-get install"] = true + if _, ok := seen["apt-get install"]; containsAptGetInstall(trimmed) && !ok { + seen["apt-get install"] = struct{}{} violations = append(violations, "apt-get install") } } diff --git a/pkg/workflow/safe_jobs.go b/pkg/workflow/safe_jobs.go index 4b626aeecb8..b93b818babf 100644 --- a/pkg/workflow/safe_jobs.go +++ b/pkg/workflow/safe_jobs.go @@ -3,7 +3,7 @@ package workflow import ( "fmt" "maps" - "sort" + "slices" "strings" "github.com/github/gh-aw/pkg/constants" @@ -201,7 +201,7 @@ func (c *Compiler) buildSafeJobs(data *WorkflowData, threatDetectionEnabled bool for rawName, cfg := range data.SafeOutputs.Jobs { entries = append(entries, safeJobEntry{stringutil.NormalizeSafeOutputIdentifier(rawName), cfg}) } - sort.Slice(entries, func(i, j int) bool { return entries[i].normalizedName < entries[j].normalizedName }) + slices.SortFunc(entries, func(a, b safeJobEntry) int { return strings.Compare(a.normalizedName, b.normalizedName) }) for _, entry := range entries { jobConfig := entry.config diff --git a/pkg/workflow/safe_outputs_messages_config.go b/pkg/workflow/safe_outputs_messages_config.go index bf6c97fa853..0f52135fe14 100644 --- a/pkg/workflow/safe_outputs_messages_config.go +++ b/pkg/workflow/safe_outputs_messages_config.go @@ -89,7 +89,7 @@ func parseMentionsConfig(mentions any) *MentionsConfig { if str, ok := item.(string); ok { // Normalize username by removing '@' prefix if present normalized := str - if len(str) > 0 && str[0] == '@' { + if str != "" && str[0] == '@' { normalized = str[1:] safeOutputMessagesLog.Printf("Normalized mention '%s' to '%s'", str, normalized) } @@ -108,7 +108,7 @@ func parseMentionsConfig(mentions any) *MentionsConfig { if str, ok := item.(string); ok { // Normalize team slug by removing '@' prefix if present normalized := str - if len(str) > 0 && str[0] == '@' { + if str != "" && str[0] == '@' { normalized = str[1:] safeOutputMessagesLog.Printf("Normalized team mention '%s' to '%s'", str, normalized) } diff --git a/pkg/workflow/shell.go b/pkg/workflow/shell.go index 8abd15fc349..d3bd3e6f3c8 100644 --- a/pkg/workflow/shell.go +++ b/pkg/workflow/shell.go @@ -158,7 +158,7 @@ func buildDockerCommandWithExpandableVars(cmd string) string { var result strings.Builder result.WriteString("'") remaining := cmd - for len(remaining) > 0 { + for remaining != "" { // Find the next variable reference nextIdx := -1 nextVar := "" diff --git a/pkg/workflow/strings.go b/pkg/workflow/strings.go index 37c638eb5bb..155c787a3e3 100644 --- a/pkg/workflow/strings.go +++ b/pkg/workflow/strings.go @@ -81,6 +81,7 @@ package workflow import ( "fmt" "hash/fnv" + "io" "regexp" "strings" @@ -168,8 +169,8 @@ func escapeActionsSingleQuotedString(value string) string { // that two different heredocs wrapping identical content still produce distinct delimiters. func GenerateHeredocDelimiterFromContent(name string, content string) string { h := fnv.New64a() - h.Write([]byte(strings.ToUpper(name))) - h.Write([]byte(content)) + _, _ = io.WriteString(h, strings.ToUpper(name)) + _, _ = io.WriteString(h, content) tag := fmt.Sprintf("%016x", h.Sum64()) upperName := strings.ToUpper(name) if name == "" { diff --git a/pkg/workflow/xml_comments.go b/pkg/workflow/xml_comments.go index 120be8bed78..71809307d84 100644 --- a/pkg/workflow/xml_comments.go +++ b/pkg/workflow/xml_comments.go @@ -11,7 +11,7 @@ var xmlCommentsLog = logger.New("workflow:xml_comments") // removeXMLComments removes XML comments () from markdown content // while preserving comments that appear within code blocks func removeXMLComments(content string) string { - xmlCommentsLog.Printf("Removing XML comments from content: %d lines", len(strings.Split(content, "\n"))) + xmlCommentsLog.Printf("Removing XML comments from content: %d lines", strings.Count(content, "\n")+1) // Track if we're inside a code block to avoid removing comments in code lines := strings.Split(content, "\n") From c5b60a000eb0e3da763da7dba16f2d35a5502a0c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 04:27:12 +0000 Subject: [PATCH 5/8] fix: simplify seen map check in runner_topology_validation.go Check absence first for clarity, using more descriptive variable name. Address code review feedback about verbose comma-ok idiom. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/runner_topology_validation.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/workflow/runner_topology_validation.go b/pkg/workflow/runner_topology_validation.go index 7f237d64b93..6f05dbd1345 100644 --- a/pkg/workflow/runner_topology_validation.go +++ b/pkg/workflow/runner_topology_validation.go @@ -86,11 +86,11 @@ func findRootRequiringPatterns(content string) []string { continue } - if _, ok := seen["sudo"]; containsSudoCommand(trimmed) && !ok { + if _, alreadySeen := seen["sudo"]; !alreadySeen && containsSudoCommand(trimmed) { seen["sudo"] = struct{}{} violations = append(violations, "sudo") } - if _, ok := seen["apt-get install"]; containsAptGetInstall(trimmed) && !ok { + if _, alreadySeen := seen["apt-get install"]; !alreadySeen && containsAptGetInstall(trimmed) { seen["apt-get install"] = struct{}{} violations = append(violations, "apt-get install") } From e1b3c65697f688d1910984b60660770058072845 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 04:54:42 +0000 Subject: [PATCH 6/8] docs(adr): add draft ADR-44461 for idiomatic Go linter compliance patterns --- ...o-patterns-for-custom-linter-compliance.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/adr/44461-adopt-idiomatic-go-patterns-for-custom-linter-compliance.md diff --git a/docs/adr/44461-adopt-idiomatic-go-patterns-for-custom-linter-compliance.md b/docs/adr/44461-adopt-idiomatic-go-patterns-for-custom-linter-compliance.md new file mode 100644 index 00000000000..6804058355f --- /dev/null +++ b/docs/adr/44461-adopt-idiomatic-go-patterns-for-custom-linter-compliance.md @@ -0,0 +1,45 @@ +# ADR-44461: Adopt Idiomatic Go Patterns for Custom Linter Compliance + +**Date**: 2026-07-09 +**Status**: Draft +**Deciders**: Unknown (automated PR by Copilot SWE Agent) + +--- + +### Context + +The repository maintains a bespoke `make golint-custom` suite of `go/analysis` linters (see `pkg/linters/`) that enforce project-wide Go idioms. Over time, the codebase accumulated ~128 new lines of business logic that contained patterns flagged by these linters. The accumulated diagnostics fell into five categories across `pkg/cli`, `pkg/workflow`, `pkg/parser`, and `pkg/logger`: (1) allocating `[]byte`→`string` conversions for equality (`bytes.Equal` is already covered by ADR-44389); (2) `map[string]bool` used as membership sets where `map[string]struct{}` avoids per-entry boolean allocations; (3) `h.Write([]byte(s))` hash writes that allocate unnecessarily when `io.WriteString` can be used if the writer implements `io.StringWriter`; (4) `len(s) > 0` empty-string checks where the idiomatic form is `s != ""`; and (5) bare `time.Sleep` calls that ignore context cancellation. Each category has an established preferred idiom in Go and a registered linter diagnostic. The diagnostics form ongoing noise in the CI lint step and are tracked separately from the function-length (`largefunc`) backlog. + +### Decision + +We will apply all non-`largefunc` custom linter findings in a single compliance pass across the affected packages: replace `map[string]bool` membership sets with `map[string]struct{}` (including API and test updates), switch hash writer calls from `h.Write([]byte(s))` to `io.WriteString(h, s)`, replace `len(s) > 0` guards with `s != ""` at ~50 call sites, replace bare `time.Sleep` with a context-aware `select { case <-time.After(d): case <-ctx.Done(): }` pattern in `mcp_inspect.go`, and replace untyped `sort.Slice` with type-safe `slices.SortFunc`. Discarded `json.Unmarshal` errors in test files are also handled. The pass brings `make golint-custom` output to zero non-`largefunc` diagnostics. + +### Alternatives Considered + +#### Alternative 1: Suppress or Disable the Linter Rules + +Linter rules could be suppressed per-file (`//nolint:lintname`) or removed from the custom suite, treating the flagged patterns as acceptable. This was rejected because the patterns are flagged for real reasons (unnecessary allocations, non-idiomatic style, context-ignoring sleeps), and suppressing them would permanently hide future regressions of the same patterns in new code. + +#### Alternative 2: Fix One Category at a Time Across Separate PRs + +Each pattern category could be addressed in its own PR, allowing focused review. This was rejected in favor of a single consolidated pass because (a) the categories are mechanically similar and share the same motivation (linter compliance), (b) splitting them would produce more merge conflicts and more CI runs for equivalent net value, and (c) the individual changes are small enough that a single PR remains reviewable. + +### Consequences + +#### Positive +- `make golint-custom` produces zero non-`largefunc` diagnostics after this PR, eliminating CI noise and providing a clean baseline. +- `map[string]struct{}` sets avoid storing a redundant `bool` value per entry; `io.WriteString` avoids a `[]byte` heap allocation per hash write call. +- The context-aware shutdown in `mcp_inspect.go` correctly respects cancellation instead of blocking the goroutine for the full sleep duration. +- Typed `slices.SortFunc` surfaces type errors at compile time that `sort.Slice` would not catch. + +#### Negative +- `map[string]struct{}` is more syntactically verbose than `map[string]bool`; set membership checks require the two-value `_, ok := m[k]` idiom instead of the direct boolean `m[k]`. +- The breadth of the change (57 files, ~128 additions) increases the review surface and raises the probability of a rebase conflict if another branch touches the same files. + +#### Neutral +- The `map[string]struct{}` API change in `action_resolver.go` and `action_cache.go` is breaking at the Go type level; all internal callers and tests are updated in the same PR. +- `io.WriteString` falls back to a plain `Write([]byte(s))` call if the writer does not implement `io.StringWriter`; behavior is identical, only the allocation path differs. + +--- + +*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.* From 77d53e1532637d00aeb81ac871bdd7432071788a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 05:09:58 +0000 Subject: [PATCH 7/8] fix: add comment explaining intentional map-first ordering in runner_topology_validation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/runner_topology_validation.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/workflow/runner_topology_validation.go b/pkg/workflow/runner_topology_validation.go index 6f05dbd1345..cc9999f970d 100644 --- a/pkg/workflow/runner_topology_validation.go +++ b/pkg/workflow/runner_topology_validation.go @@ -86,6 +86,8 @@ func findRootRequiringPatterns(content string) []string { continue } + // Map is checked first to short-circuit the more expensive regex scan once a + // violation has already been recorded for this key. if _, alreadySeen := seen["sudo"]; !alreadySeen && containsSudoCommand(trimmed) { seen["sudo"] = struct{}{} violations = append(violations, "sudo") From 461d16fa59c36a52380520f05d4310e782a08766 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 10:10:59 +0000 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20address=20review=20feedback=20?= =?UTF-8?q?=E2=80=94=20handle=20errors=20in=20dependabot=5Ftest.go,=20add?= =?UTF-8?q?=20io.WriteString=20comment=20in=20strings.go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/workflow/dependabot_test.go | 14 +++++++++++--- pkg/workflow/strings.go | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/workflow/dependabot_test.go b/pkg/workflow/dependabot_test.go index c844fb5bceb..4ebfa3b9aed 100644 --- a/pkg/workflow/dependabot_test.go +++ b/pkg/workflow/dependabot_test.go @@ -337,9 +337,14 @@ func TestGenerateDependabotConfig_PreserveExisting(t *testing.T) { } // Verify file was preserved (no error means it was skipped) - data, _ := os.ReadFile(dependabotPath) + data, err := os.ReadFile(dependabotPath) + if err != nil { + t.Fatalf("failed to read dependabot.yml: %v", err) + } var config DependabotConfig - yaml.Unmarshal(data, &config) + if err := yaml.Unmarshal(data, &config); err != nil { + t.Fatalf("failed to unmarshal dependabot.yml: %v", err) + } if len(config.Updates) != 1 { t.Error("existing config should be preserved without force flag") } @@ -603,7 +608,10 @@ func TestGenerateDependabotManifests_WithDependencies(t *testing.T) { } // Verify package.json content - data, _ := os.ReadFile(packageJSONPath) + data, err := os.ReadFile(packageJSONPath) + if err != nil { + t.Fatalf("failed to read package.json: %v", err) + } var pkgJSON PackageJSON if err := json.Unmarshal(data, &pkgJSON); err != nil { t.Fatalf("failed to unmarshal package.json: %v", err) diff --git a/pkg/workflow/strings.go b/pkg/workflow/strings.go index 155c787a3e3..6274b48c3c2 100644 --- a/pkg/workflow/strings.go +++ b/pkg/workflow/strings.go @@ -169,6 +169,7 @@ func escapeActionsSingleQuotedString(value string) string { // that two different heredocs wrapping identical content still produce distinct delimiters. func GenerateHeredocDelimiterFromContent(name string, content string) string { h := fnv.New64a() + // hash.Hash.Write never returns an error in practice, but check to satisfy gosec G104 _, _ = io.WriteString(h, strings.ToUpper(name)) _, _ = io.WriteString(h, content) tag := fmt.Sprintf("%016x", h.Sum64())