diff --git a/.github/workflows/gh-aw-estc-pr-buildkite-detective.lock.yml b/.github/workflows/gh-aw-estc-pr-buildkite-detective.lock.yml index db31faca..c771c9b6 100644 --- a/.github/workflows/gh-aw-estc-pr-buildkite-detective.lock.yml +++ b/.github/workflows/gh-aw-estc-pr-buildkite-detective.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"de7217ff42d2a3fd3d1a7db9feb9134830a4b2f45bb89e7dcfd1ac6230d3c81b","body_hash":"9cd3b52ebec5747dd266ff2460983488ce6e37e069cb591cd606ada5c5e88df9","compiler_version":"v0.87.5","agent_id":"copilot","agent_model":"${{ inputs.model }}","engine_versions":{"copilot":"1.0.80"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"ae1860e3aa239db5d6db7845f84a1e17269206f45ff9c9d5f2d26ba3a5e1c0ad","body_hash":"9cd3b52ebec5747dd266ff2460983488ce6e37e069cb591cd606ada5c5e88df9","compiler_version":"v0.87.5","agent_id":"copilot","agent_model":"${{ inputs.model }}","engine_versions":{"copilot":"1.0.80"}} # gh-aw-manifest: {"version":1,"secrets":["BUILDKITE_API_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":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-go","sha":"40f1582b2485089dde7abd97c1529aa768e1baff","version":"v5"},{"repo":"actions/setup-node","sha":"249970729cb0ef3589644e2896645e5dc5ba9c38","version":"v6"},{"repo":"actions/setup-python","sha":"a26af69be951a213d495a4c3e4e4022e16d87065","version":"v5"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"astral-sh/setup-uv","sha":"d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86","version":"v5"},{"repo":"github/gh-aw-actions/setup-cli","sha":"2a78d04403fdc6907d0f05327cffac9dbad5312d","version":"v0.87.5"},{"repo":"github/gh-aw/actions/setup","sha":"654cf351a5953df1f2751f49de8a9ca384c07994","version":"v0.87.5"},{"repo":"ruby/setup-ruby","sha":"95ef2b042f9d7a56d8268cba8559e2842e2ad01b","version":"v1.321.0 (source v1)"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.7","digest":"sha256:40a1e30b1b8d70642d4292485146cd5af612730d7a6a2e12706ddd13df375059","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.7@sha256:40a1e30b1b8d70642d4292485146cd5af612730d7a6a2e12706ddd13df375059"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.7","digest":"sha256:4f209dd4cbc74d47a6c7379956143de293429d1b1b2fb2647776cdcbf65836a1","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.7@sha256:4f209dd4cbc74d47a6c7379956143de293429d1b1b2fb2647776cdcbf65836a1"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.7","digest":"sha256:fb362a08d4d2f0da6c036e3f5d3b2fd87931e857fec3ca4a241cd2f2b61131f9","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.7@sha256:fb362a08d4d2f0da6c036e3f5d3b2fd87931e857fec3ca4a241cd2f2b61131f9"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.10","digest":"sha256:08bb5fa417aed94b40a14e2b7b3ae457531a5f22b143a32fe58317139d9b8f42","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.10@sha256:08bb5fa417aed94b40a14e2b7b3ae457531a5f22b143a32fe58317139d9b8f42"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e","pinned_image":"ghcr.io/github/gh-aw-node@sha256:bac2192f6374d6262116399b34fc5e143d576f82719e90a18261cae7480f4d4e"},{"image":"ghcr.io/github/github-mcp-server:v1.10.0","digest":"sha256:097512ddf58af80a620c177ae9cad93448f9a2a55c70ee8fde5cec6714522a8c","pinned_image":"ghcr.io/github/github-mcp-server:v1.10.0@sha256:097512ddf58af80a620c177ae9cad93448f9a2a55c70ee8fde5cec6714522a8c"}],"mcp_servers":[{"name":"github","tools":["actions_get","actions_list","get_commit","get_file_contents","get_job_logs","get_latest_release","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"public-code-search","tools":["search_code"]},{"name":"safeoutputs","tools":["add_comment","missing_data","missing_tool","noop"]}]} # This file was automatically generated by gh-aw (v0.87.5). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -570,7 +570,7 @@ jobs: GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_RUN_ID: ${{ github.run_id }} name: Resolve event context and fetch Buildkite data - run: "python3 - << 'PYEOF'\nimport json, os, re, subprocess, sys, urllib.request\n\nBK_TOKEN = os.environ['BUILDKITE_API_TOKEN']\nEVENT_NAME = os.environ['GITHUB_EVENT_NAME']\nLOG_TAIL = 150\nANSI_RE = re.compile(r'\\x1b(?:\\[[0-9;]*[A-Za-z]|_[^\\x07]*\\x07|[()][AB012]|[=>])')\nBK_URL_RE = re.compile(r'https://buildkite\\.com/([^/]+)/([^/]+)/builds/(\\d+)')\n\nos.makedirs('/tmp/gh-aw/buildkite-logs', exist_ok=True)\n\ndef bk_get(path):\n url = f'https://api.buildkite.com/v2/{path}'\n req = urllib.request.Request(url, headers={'Authorization': f'Bearer {BK_TOKEN}'})\n with urllib.request.urlopen(req) as r:\n return json.load(r)\n\ndef slugify(name):\n return re.sub(r'\\s+', '-', re.sub(r'[^\\w\\s-]', '', name).strip()).lower()[:60].strip('-')\n\nwith open(os.environ['GITHUB_EVENT_PATH']) as f:\n event = json.load(f)\n\nif EVENT_NAME == 'status':\n commit_sha = event['commit']['sha']\n target_url = event.get('target_url', '')\nelse:\n cr = event['check_run']\n commit_sha = cr['head_sha']\n target_url = cr.get('details_url', '')\n\nm = BK_URL_RE.search(target_url)\nif not m:\n print(f'No Buildkite build URL in target_url: {target_url}')\n sys.exit(1)\n\nbk_org, bk_pipeline, bk_number = m.group(1), m.group(2), m.group(3)\nbuild = bk_get(f'organizations/{bk_org}/pipelines/{bk_pipeline}/builds/{bk_number}')\n\npr_info = build.get('pull_request') or {}\npr_number = pr_info.get('id', '')\nbranch = build.get('branch', '')\n\nwith open('/tmp/gh-aw/buildkite-event.txt', 'w') as f:\n f.write(f'event_name: {EVENT_NAME}\\n')\n f.write(f'commit_sha: {commit_sha}\\n')\n f.write(f'build_url: {m.group(0)}\\n')\n f.write(f'pipeline: {bk_pipeline}\\n')\n f.write(f'branch: {branch}\\n')\n f.write(f'pr_number: {pr_number}\\n')\nprint('Buildkite event context:')\nprint(open('/tmp/gh-aw/buildkite-event.txt').read())\n\ndef skip(reason):\n subprocess.run(['bash', '-c', f'echo \"::notice::{reason}\"'], check=False)\n print(reason)\n sys.exit(1)\n\nif not pr_number:\n skip('Build is not associated with a PR; skipping')\n\nif build['state'] not in ('failed', 'failing'):\n skip(f'Build is not finished (state: {build[\"state\"]}); skipping')\n\ndef collect_failed_jobs(build_data, pipeline_slug, build_url):\n \"\"\"Collect failed script jobs, following trigger jobs to child builds.\"\"\"\n FAIL_STATES = ('failed', 'timed_out')\n results = []\n\n for job in build_data.get('jobs', []):\n if job.get('state') not in FAIL_STATES:\n continue\n if job.get('type') == 'script':\n results.append((pipeline_slug, build_url, job))\n elif job.get('type') == 'trigger':\n triggered = job.get('triggered_build') or {}\n child_url = triggered.get('web_url', '')\n cm = BK_URL_RE.search(child_url)\n if cm:\n child = bk_get(f'organizations/{cm.group(1)}/pipelines/{cm.group(2)}/builds/{cm.group(3)}')\n results.extend(collect_failed_jobs(child, cm.group(2), child_url))\n\n return results\n\nfailed = collect_failed_jobs(build, bk_pipeline, m.group(0))\nif not failed:\n skip(f'No failed script jobs in build (build state: {build[\"state\"]})')\n\nsummary = [\n f'## Build: {m.group(0)}',\n f'Pipeline: {bk_pipeline} State: {build[\"state\"]}',\n f'PR: #{pr_number} Branch: {branch}',\n f'Failed jobs: {len(failed)}',\n '',\n]\n\nfor pipeline_slug, build_url, job in failed:\n slug = slugify(job.get('name', f'job-{job[\"id\"]}'))\n log_file = f'/tmp/gh-aw/buildkite-logs/{pipeline_slug}-{slug}.txt'\n\n summary.append(f'### {job[\"name\"]}')\n summary.append(f'Pipeline: {pipeline_slug} Build: {build_url}')\n summary.append(f'State: {job[\"state\"]} Exit status: {job.get(\"exit_status\")}')\n summary.append(f'Command: {job.get(\"command\", \"\").strip()}')\n summary.append(f'Log: {log_file}')\n summary.append('')\n\n raw_url = job.get('raw_log_url', '')\n if raw_url:\n req = urllib.request.Request(raw_url, headers={'Authorization': f'Bearer {BK_TOKEN}'})\n with urllib.request.urlopen(req) as r:\n lines = r.read().decode('utf-8', errors='replace').splitlines()\n with open(log_file, 'w') as f:\n f.write('\\n'.join(ANSI_RE.sub('', l) for l in lines[-LOG_TAIL:]))\n\nwith open('/tmp/gh-aw/buildkite-failures.txt', 'w') as f:\n f.write('\\n'.join(summary))\nprint(f'Fetched {len(failed)} failed job(s)')\nPYEOF\n" + run: "python3 - << 'PYEOF'\nimport json, os, re, subprocess, sys, urllib.request\n\nBK_TOKEN = os.environ['BUILDKITE_API_TOKEN']\nEVENT_NAME = os.environ['GITHUB_EVENT_NAME']\nLOG_TAIL = 150\nANSI_RE = re.compile(r'\\x1b(?:\\[[0-9;]*[A-Za-z]|_[^\\x07]*\\x07|[()][AB012]|[=>])')\nBK_URL_RE = re.compile(r'https://buildkite\\.com/([^/]+)/([^/]+)/builds/(\\d+)')\n\nos.makedirs('/tmp/gh-aw/buildkite-logs', exist_ok=True)\n\ndef bk_get(path):\n url = f'https://api.buildkite.com/v2/{path}'\n req = urllib.request.Request(url, headers={'Authorization': f'Bearer {BK_TOKEN}'})\n with urllib.request.urlopen(req) as r:\n return json.load(r)\n\ndef slugify(name):\n return re.sub(r'\\s+', '-', re.sub(r'[^\\w\\s-]', '', name).strip()).lower()[:60].strip('-')\n\nwith open(os.environ['GITHUB_EVENT_PATH']) as f:\n event = json.load(f)\n\nif EVENT_NAME == 'status':\n commit_sha = event['commit']['sha']\n target_url = event.get('target_url', '')\nelse:\n cr = event['check_run']\n commit_sha = cr['head_sha']\n target_url = cr.get('details_url', '')\n\nm = BK_URL_RE.search(target_url)\nif not m:\n print(f'No Buildkite build URL in target_url: {target_url}')\n sys.exit(1)\n\nbk_org, bk_pipeline, bk_number = m.group(1), m.group(2), m.group(3)\nbuild = bk_get(f'organizations/{bk_org}/pipelines/{bk_pipeline}/builds/{bk_number}')\n\npr_info = build.get('pull_request') or {}\npr_number = pr_info.get('id', '')\nbranch = build.get('branch', '')\n\nwith open('/tmp/gh-aw/buildkite-event.txt', 'w') as f:\n f.write(f'event_name: {EVENT_NAME}\\n')\n f.write(f'commit_sha: {commit_sha}\\n')\n f.write(f'build_url: {m.group(0)}\\n')\n f.write(f'pipeline: {bk_pipeline}\\n')\n f.write(f'branch: {branch}\\n')\n f.write(f'pr_number: {pr_number}\\n')\nprint('Buildkite event context:')\nprint(open('/tmp/gh-aw/buildkite-event.txt').read())\n\ndef skip(reason):\n subprocess.run(['bash', '-c', f'echo \"::notice::{reason}\"'], check=False)\n print(reason)\n sys.exit(0)\n\nif not pr_number:\n skip('Build is not associated with a PR; skipping')\n\nif build['state'] not in ('failed', 'failing'):\n skip(f'Build is not finished (state: {build[\"state\"]}); skipping')\n\ndef collect_failed_jobs(build_data, pipeline_slug, build_url):\n \"\"\"Collect failed script jobs, following trigger jobs to child builds.\"\"\"\n FAIL_STATES = ('failed', 'timed_out')\n results = []\n\n for job in build_data.get('jobs', []):\n if job.get('state') not in FAIL_STATES:\n continue\n if job.get('type') == 'script':\n results.append((pipeline_slug, build_url, job))\n elif job.get('type') == 'trigger':\n triggered = job.get('triggered_build') or {}\n child_url = triggered.get('web_url', '')\n cm = BK_URL_RE.search(child_url)\n if cm:\n child = bk_get(f'organizations/{cm.group(1)}/pipelines/{cm.group(2)}/builds/{cm.group(3)}')\n results.extend(collect_failed_jobs(child, cm.group(2), child_url))\n\n return results\n\nfailed = collect_failed_jobs(build, bk_pipeline, m.group(0))\nif not failed:\n skip(f'No failed script jobs in build (build state: {build[\"state\"]})')\n\nsummary = [\n f'## Build: {m.group(0)}',\n f'Pipeline: {bk_pipeline} State: {build[\"state\"]}',\n f'PR: #{pr_number} Branch: {branch}',\n f'Failed jobs: {len(failed)}',\n '',\n]\n\nfor pipeline_slug, build_url, job in failed:\n slug = slugify(job.get('name', f'job-{job[\"id\"]}'))\n log_file = f'/tmp/gh-aw/buildkite-logs/{pipeline_slug}-{slug}.txt'\n\n summary.append(f'### {job[\"name\"]}')\n summary.append(f'Pipeline: {pipeline_slug} Build: {build_url}')\n summary.append(f'State: {job[\"state\"]} Exit status: {job.get(\"exit_status\")}')\n summary.append(f'Command: {job.get(\"command\", \"\").strip()}')\n summary.append(f'Log: {log_file}')\n summary.append('')\n\n raw_url = job.get('raw_log_url', '')\n if raw_url:\n req = urllib.request.Request(raw_url, headers={'Authorization': f'Bearer {BK_TOKEN}'})\n with urllib.request.urlopen(req) as r:\n lines = r.read().decode('utf-8', errors='replace').splitlines()\n with open(log_file, 'w') as f:\n f.write('\\n'.join(ANSI_RE.sub('', l) for l in lines[-LOG_TAIL:]))\n\nwith open('/tmp/gh-aw/buildkite-failures.txt', 'w') as f:\n f.write('\\n'.join(summary))\nprint(f'Fetched {len(failed)} failed job(s)')\nPYEOF\n" - env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/gh-aw-estc-pr-buildkite-detective.md b/.github/workflows/gh-aw-estc-pr-buildkite-detective.md index 7d6efba3..0a2a6303 100644 --- a/.github/workflows/gh-aw-estc-pr-buildkite-detective.md +++ b/.github/workflows/gh-aw-estc-pr-buildkite-detective.md @@ -139,7 +139,7 @@ steps: def skip(reason): subprocess.run(['bash', '-c', f'echo "::notice::{reason}"'], check=False) print(reason) - sys.exit(1) + sys.exit(0) if not pr_number: skip('Build is not associated with a PR; skipping')