From 9fad94807594465c54923559d86b77d8ea0d821f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 6 May 2026 11:15:15 -0700 Subject: [PATCH 01/16] Convert module cleanup to GitHub agentic workflow --- .gitattributes | 2 + .github/aw/actions-lock.json | 14 + .../scripts/module-cleanup/cleanup-loop.py | 297 --- .../scripts/module-cleanup/extract-report.py | 74 - .../module-cleanup/jsonl-diagnostics.py | 122 -- .github/scripts/module-cleanup/pr-body.py | 111 -- .github/workflows/module-cleanup.lock.yml | 1597 +++++++++++++++++ .github/workflows/module-cleanup.md | 208 +++ .github/workflows/module-cleanup.yml | 226 --- 9 files changed, 1821 insertions(+), 830 deletions(-) create mode 100644 .github/aw/actions-lock.json delete mode 100644 .github/scripts/module-cleanup/cleanup-loop.py delete mode 100644 .github/scripts/module-cleanup/extract-report.py delete mode 100644 .github/scripts/module-cleanup/jsonl-diagnostics.py delete mode 100644 .github/scripts/module-cleanup/pr-body.py create mode 100644 .github/workflows/module-cleanup.lock.yml create mode 100644 .github/workflows/module-cleanup.md delete mode 100644 .github/workflows/module-cleanup.yml diff --git a/.gitattributes b/.gitattributes index 3982c9ad9a59..988d7a1f8843 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,5 @@ *.cmd text eol=crlf licenses/** linguist-generated + +.github/workflows/*.lock.yml linguist-generated=true merge=ours diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json new file mode 100644 index 000000000000..415337d210ea --- /dev/null +++ b/.github/aw/actions-lock.json @@ -0,0 +1,14 @@ +{ + "entries": { + "actions/github-script@v9.0.0": { + "repo": "actions/github-script", + "version": "v9.0.0", + "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" + }, + "github/gh-aw-actions/setup@v0.71.5": { + "repo": "github/gh-aw-actions/setup", + "version": "v0.71.5", + "sha": "b8068426813005612b960b5ab0b8bd2c27142323" + } + } +} diff --git a/.github/scripts/module-cleanup/cleanup-loop.py b/.github/scripts/module-cleanup/cleanup-loop.py deleted file mode 100644 index eee335da2349..000000000000 --- a/.github/scripts/module-cleanup/cleanup-loop.py +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env python3 -"""Walk instrumentation modules sequentially, applying Copilot cleanup fixes. - -Invoked by the `module-cleanup` workflow's `Run Copilot cleanup loop` step. -Reads the dispatch list from MODULES_JSON, invokes the `copilot` CLI per -module, squashes each module's edits into a single commit on the current -branch, and stops once the total number of files modified versus `origin/main` -reaches FILE_THRESHOLD. - -Per-module diagnostics (raw JSONL, extracted report, final assistant message, -JSONL diagnostics, and a PR body fragment) are written under COPILOT_ROOT, -and the ordered list of modules actually processed (including no-op ones) is -written to PROCESSED_MODULES so the workflow can append them to `reviewed.txt`. - -Environment variables (all required): - MODULES_JSON - JSON array of {short_name, module_dir} - FILE_THRESHOLD - integer; stop after this many modified files - MODEL - Copilot model name to pass via `--model` - COPILOT_ROOT - directory for per-module diagnostics - FRAGMENTS_DIR - directory for per-module PR body fragments - PROCESSED_MODULES - path to file receiving processed short_names - COPILOT_REVIEW_PROMPT_TEMPLATE - prompt with `__MODULE_DIR__` placeholder - COPILOT_GITHUB_TOKEN - Copilot CLI auth token - GITHUB_OUTPUT - GitHub Actions output file -""" - -from __future__ import annotations - -import json -import os -import subprocess -import sys -from pathlib import Path - -SCRIPTS_DIR = Path(__file__).resolve().parent -EXTRACT_REPORT_SCRIPT = SCRIPTS_DIR / "extract-report.py" -JSONL_DIAGNOSTICS_SCRIPT = SCRIPTS_DIR / "jsonl-diagnostics.py" -PR_BODY_SCRIPT = SCRIPTS_DIR / "pr-body.py" - - -def require_env(name: str) -> str: - value = os.environ.get(name) - if value is None or value == "": - raise SystemExit(f"Missing required environment variable: {name}") - return value - - -def write_github_output(key: str, value: str) -> None: - with open(require_env("GITHUB_OUTPUT"), "a", encoding="utf-8") as f: - f.write(f"{key}={value}\n") - - -def run_git(*args: str, capture: bool = False) -> subprocess.CompletedProcess: - return subprocess.run( - ["git", *args], - check=True, - text=True, - capture_output=capture, - ) - - -def current_head_sha() -> str: - return run_git("rev-parse", "HEAD", capture=True).stdout.strip() - - -def count_modified_files_vs_main() -> int: - result = run_git("diff", "--name-only", "origin/main", capture=True) - return sum(1 for line in result.stdout.splitlines() if line.strip()) - - -def count_commits_since_main() -> int: - result = run_git("rev-list", "--count", "origin/main..HEAD", capture=True) - return int(result.stdout) - - -def staged_changes_present() -> bool: - # `git diff --cached --quiet` exits 0 if no staged changes, 1 if there are. - return subprocess.run(["git", "diff", "--cached", "--quiet"], check=False).returncode != 0 - - -def run_copilot(prompt: str, model: str, output_path: Path) -> int: - """Invoke the Copilot CLI, streaming JSONL to `output_path`. Returns exit code.""" - with output_path.open("wb") as out: - # `--yolo` disables Copilot CLI confirmation prompts for tool calls. - # This is intentional for autonomous CI and must not be copied into - # interactive/local scripts without review. - proc = subprocess.run( - [ - "copilot", - "-p", prompt, - "--agent", "module-cleanup", - "--model", model, - "--output-format", "json", - "--silent", - "--stream", "off", - "--yolo", - ], - stdout=out, - check=False, - # stderr inherits the workflow's stderr so Actions logs show live output. - ) - return proc.returncode - - -def run_extract_report( - *, - copilot_output: Path, - final_message: Path, - review_report: Path, -) -> int: - return subprocess.run( - [ - sys.executable, str(EXTRACT_REPORT_SCRIPT), - "--input", str(copilot_output), - "--final-message-output", str(final_message), - "--output", str(review_report), - ], - check=False, - ).returncode - - -def write_diagnostics(copilot_output: Path, diagnostics: Path) -> None: - with diagnostics.open("wb") as out: - subprocess.run( - [sys.executable, str(JSONL_DIAGNOSTICS_SCRIPT), "--input", str(copilot_output)], - stdout=out, - check=False, - ) - - -def render_pr_body_fragment( - *, - fragment_path: Path, - short_name: str, - module_dir: str, - review_report: Path, -) -> None: - body_file = fragment_path.with_suffix(fragment_path.suffix + ".body") - subprocess.run( - [ - sys.executable, str(PR_BODY_SCRIPT), - "--input", str(review_report), - "--output", str(body_file), - ], - check=True, - ) - header = ( - f"## Module: `{short_name}`\n" - f"\n" - f"_Module path: `{module_dir}`_\n" - f"\n" - ) - fragment_path.write_text(header + body_file.read_text(encoding="utf-8"), encoding="utf-8") - body_file.unlink() - - -def process_module( - *, - short_name: str, - module_dir: str, - fragment_index: int, - copilot_root: Path, - fragments_dir: Path, - processed_modules_file: Path, - model: str, - prompt_template: str, -) -> bool: - """Process one module. - - Returns True if the module ran to completion (even with zero edits), - False if Copilot or report extraction failed and the module should be - retried on a future run. - """ - slug = short_name.replace(":", "-") - work_dir = copilot_root / slug - work_dir.mkdir(parents=True, exist_ok=True) - - copilot_output = work_dir / "copilot-output.jsonl" - final_message = work_dir / "final-assistant-message.txt" - review_report = work_dir / "review-report.json" - diagnostics = work_dir / "diagnostics.txt" - - print(f"::group::Copilot review ({model}) for {module_dir}", flush=True) - try: - prompt = prompt_template.replace("__MODULE_DIR__", module_dir) - pre_run_sha = current_head_sha() - - copilot_rc = run_copilot(prompt, model, copilot_output) - - # Diagnostics are best-effort and should be produced even if Copilot failed. - write_diagnostics(copilot_output, diagnostics) - - if copilot_rc != 0: - print( - f"Copilot invocation for {short_name} failed with exit {copilot_rc};" - " skipping (module will be retried next run)." - ) - run_git("reset", "--hard", pre_run_sha) - return False - - extract_rc = run_extract_report( - copilot_output=copilot_output, - final_message=final_message, - review_report=review_report, - ) - if extract_rc != 0: - print( - f"Report extraction failed for {short_name} (exit {extract_rc});" - " discarding edits and skipping." - ) - run_git("reset", "--hard", pre_run_sha) - return False - - # Squash whatever Copilot committed for this module into one commit on our branch. - run_git("reset", "--soft", pre_run_sha) - run_git("add", "-A") - - # Record this module as processed regardless of whether it produced edits, - # so no-op modules aren't re-walked on the next run. - with processed_modules_file.open("a", encoding="utf-8") as f: - f.write(short_name + "\n") - - fragment_path = fragments_dir / f"{fragment_index:03d}-{slug}.md" - render_pr_body_fragment( - fragment_path=fragment_path, - short_name=short_name, - module_dir=module_dir, - review_report=review_report, - ) - - if staged_changes_present(): - run_git( - "commit", - "-m", f"Cleanup for {short_name}", - "-m", f"Automated module cleanup of {module_dir}.", - ) - else: - print(f"No edits from {short_name}; not committing.") - - total_changed = count_modified_files_vs_main() - print(f"Total modified files so far: {total_changed}") - return True - finally: - print("::endgroup::", flush=True) - - -def main() -> None: - modules_json = require_env("MODULES_JSON") - file_threshold = int(require_env("FILE_THRESHOLD")) - model = require_env("MODEL") - copilot_root = Path(require_env("COPILOT_ROOT")) - fragments_dir = Path(require_env("FRAGMENTS_DIR")) - processed_modules_file = Path(require_env("PROCESSED_MODULES")) - prompt_template = require_env("COPILOT_REVIEW_PROMPT_TEMPLATE") - - modules: list[dict[str, str]] = json.loads(modules_json) - - copilot_root.mkdir(parents=True, exist_ok=True) - fragments_dir.mkdir(parents=True, exist_ok=True) - processed_modules_file.write_text("", encoding="utf-8") - - print(f"Modules to walk (upper bound for this run): {len(modules)}") - - fragment_index = 0 - for module in modules: - processed = process_module( - short_name=module["short_name"], - module_dir=module["module_dir"], - fragment_index=fragment_index, - copilot_root=copilot_root, - fragments_dir=fragments_dir, - processed_modules_file=processed_modules_file, - model=model, - prompt_template=prompt_template, - ) - if processed: - fragment_index += 1 - - total_changed = count_modified_files_vs_main() - if total_changed >= file_threshold: - print( - f"Reached file threshold ({total_changed} >= {file_threshold});" - " stopping loop." - ) - break - - processed_lines = processed_modules_file.read_text(encoding="utf-8").splitlines() - processed_count = sum(1 for line in processed_lines if line.strip()) - commits_on_branch = count_commits_since_main() - print(f"Processed modules: {processed_count}") - print(f"Commits on cleanup branch: {commits_on_branch}") - write_github_output("processed_count", str(processed_count)) - write_github_output("commits_on_branch", str(commits_on_branch)) - - -if __name__ == "__main__": - main() diff --git a/.github/scripts/module-cleanup/extract-report.py b/.github/scripts/module-cleanup/extract-report.py deleted file mode 100644 index 32c65b1c0cb3..000000000000 --- a/.github/scripts/module-cleanup/extract-report.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 -"""Extract the final assistant message from review CLI JSONL output.""" - -from __future__ import annotations - -import argparse -import json -from pathlib import Path - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser() - parser.add_argument("--input", required=True) - parser.add_argument("--output", required=True) - parser.add_argument("--final-message-output") - return parser.parse_args() - - -def strip_json_fence(value: str) -> str: - stripped = value.strip() - lines = stripped.splitlines() - if ( - len(lines) >= 3 - and lines[0].strip().lower() in {"```", "```json"} - and lines[-1].strip().startswith("```") - ): - return "\n".join(lines[1:-1]).strip() - return stripped - - -def extract_final_message(path: Path) -> str: - final_message: str | None = None - - with path.open(encoding="utf-8") as handle: - for raw_line in handle: - line = raw_line.strip() - if not line: - continue - event = json.loads(line) - if event.get("type") != "assistant.message": - continue - content = event["data"]["content"] - if isinstance(content, str): - final_message = content - - if not final_message or not final_message.strip(): - raise ValueError("Review output did not contain a non-empty assistant.message") - - return final_message.strip() - - -def validate_report_json(report: str) -> dict[str, object]: - parsed = json.loads(strip_json_fence(report)) - if not isinstance(parsed, dict): - raise ValueError(f"Review report must be a JSON object, got {type(parsed).__name__}") - return parsed - - -def main() -> None: - args = parse_args() - report = extract_final_message(Path(args.input)) - - # Write the raw final message *before* JSON validation so that, on validation - # failure, the diagnostic artifact is preserved for post-mortem. In that case - # `--output` is intentionally not produced and the caller treats the run as failed. - if args.final_message_output: - Path(args.final_message_output).write_text(report + "\n", encoding="utf-8") - - parsed = validate_report_json(report) - Path(args.output).write_text(json.dumps(parsed, indent=2) + "\n", encoding="utf-8") - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/.github/scripts/module-cleanup/jsonl-diagnostics.py b/.github/scripts/module-cleanup/jsonl-diagnostics.py deleted file mode 100644 index 400fa75d491d..000000000000 --- a/.github/scripts/module-cleanup/jsonl-diagnostics.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python3 -"""Print concise diagnostics for Copilot review CLI JSONL output.""" - -from __future__ import annotations - -import argparse -import json -from collections import Counter, deque -from pathlib import Path - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser() - parser.add_argument("--input", required=True) - parser.add_argument("--tail", type=int, default=15) - return parser.parse_args() - - -def collapse(value: str, limit: int = 240) -> str: - collapsed = " ".join(value.split()) - if len(collapsed) <= limit: - return collapsed - return collapsed[: limit - 3] + "..." - - -def summarize_field(value: object) -> str: - if isinstance(value, str): - return collapse(value) - if isinstance(value, bool): - return str(value).lower() - if isinstance(value, (int, float)): - return str(value) - if value is None: - return "null" - if isinstance(value, list): - return f"list[{len(value)}]" - if isinstance(value, dict): - return f"object({', '.join(sorted(value.keys())[:6])})" - return type(value).__name__ - - -def summarize_event(event: dict[str, object]) -> str: - parts: list[str] = [] - data = event.get("data") - - for key in ("type", "id"): - if key in event: - parts.append(f"{key}={summarize_field(event[key])}") - - if isinstance(data, dict): - for key in ( - "role", - "name", - "tool", - "toolName", - "command", - "status", - "exitCode", - "exit_code", - "content", - "error", - "message", - ): - if key in data: - parts.append(f"{key}={summarize_field(data[key])}") - if len(parts) <= 2: - parts.append(f"dataKeys={','.join(sorted(data.keys())[:8])}") - - return "; ".join(parts) - - -def main() -> None: - args = parse_args() - path = Path(args.input) - - if not path.exists(): - print(f"Diagnostics input missing: {path}") - return - - raw_lines = path.read_text(encoding="utf-8").splitlines() - print(f"Diagnostics for {path}") - print(f"Line count: {len(raw_lines)}") - - event_types: Counter[str] = Counter() - assistant_messages: list[str] = [] - tail_events: deque[tuple[int, str]] = deque(maxlen=args.tail) - - for line_number, raw_line in enumerate(raw_lines, start=1): - line = raw_line.strip() - if not line: - continue - event = json.loads(line) - - event_type = str(event.get("type", "")) - event_types[event_type] += 1 - - data = event.get("data") - if event_type == "assistant.message" and isinstance(data, dict): - content = data.get("content") - if isinstance(content, str) and content.strip(): - assistant_messages.append(collapse(content, limit=500)) - - tail_events.append((line_number, summarize_event(event))) - - print("Event types:") - for event_type, count in event_types.most_common(): - print(f" {event_type}: {count}") - - if assistant_messages: - print("Last assistant message:") - print(f" {assistant_messages[-1]}") - else: - print("Last assistant message:") - print(" ") - - print(f"Last {len(tail_events)} events:") - for line_number, summary in tail_events: - print(f" line {line_number}: {summary}") - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/.github/scripts/module-cleanup/pr-body.py b/.github/scripts/module-cleanup/pr-body.py deleted file mode 100644 index 1156954d966b..000000000000 --- a/.github/scripts/module-cleanup/pr-body.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -"""Render a PR body from a structured review report.""" - -from __future__ import annotations - -import argparse -import json -from pathlib import Path - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser() - parser.add_argument("--input", required=True) - parser.add_argument("--output", required=True) - return parser.parse_args() - - -def strip_code_fences(raw: str) -> str: - lines = raw.strip().splitlines() - if len(lines) >= 3 and lines[0].startswith("```") and lines[-1].startswith("```"): - return "\n".join(lines[1:-1]).strip() - return raw.strip() - - -def load_report(path: Path) -> dict: - return json.loads(strip_code_fences(path.read_text(encoding="utf-8"))) - - -# Characters that can inject Markdown/HTML structure when LLM-supplied text -# ends up as PR body prose. Escape them so headings/links/emphasis/HTML can't -# be forged. -_MARKDOWN_METACHARS = r"\`*_{}[]()#+-.!|<>" -_MARKDOWN_TRANSLATION = str.maketrans({c: "\\" + c for c in _MARKDOWN_METACHARS}) - - -def escape_markdown(value: str) -> str: - return " ".join(value.translate(_MARKDOWN_TRANSLATION).split()).strip() - - -def render_path(path: str, line_hint: object) -> str: - file_name = Path(path).name - if isinstance(line_hint, int): - return f"`{file_name}:{line_hint}`" - return f"`{file_name}`" - - -def group_changes_by_category(changes: list[dict]) -> list[tuple[str, list[dict]]]: - grouped: dict[str, list[dict]] = {} - ordered_categories: list[str] = [] - for change in changes: - category = escape_markdown(change["category"]) - if category not in grouped: - grouped[category] = [] - ordered_categories.append(category) - grouped[category].append(change) - return [(category, grouped[category]) for category in ordered_categories] - - -def render_change(change: dict) -> list[str]: - return [ - f"**File:** {render_path(change['path'], change.get('line_hint'))} ", - f"**Change:** {escape_markdown(change['change'])} ", - f"**Reason:** {escape_markdown(change['reason'])}", - "", - ] - - -def render_unresolved_item(item: dict) -> list[str]: - return [ - f"**File:** {render_path(item['path'], None)} ", - f"**Reason:** {escape_markdown(item['reason'])}", - "", - ] - - -def render_body(report: dict) -> str: - lines = [ - "### Summary", - "", - report["summary"], - "", - "### Applied Changes", - "", - ] - - changes = report.get("changes") or [] - if changes: - for category, category_changes in group_changes_by_category(changes): - lines.extend([f"#### {category}", ""]) - for change in category_changes: - lines.extend(render_change(change)) - else: - lines.extend(["No safe automated changes were applied.", ""]) - - unresolved = report.get("unresolved") or [] - if unresolved: - lines.extend(["### Unresolved Items", ""]) - for item in unresolved: - lines.extend(render_unresolved_item(item)) - - return "\n".join(lines) - - -def main() -> None: - args = parse_args() - report = load_report(Path(args.input)) - Path(args.output).write_text(render_body(report), encoding="utf-8") - - -if __name__ == "__main__": - main() diff --git a/.github/workflows/module-cleanup.lock.yml b/.github/workflows/module-cleanup.lock.yml new file mode 100644 index 000000000000..7bad80f88a0a --- /dev/null +++ b/.github/workflows/module-cleanup.lock.yml @@ -0,0 +1,1597 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"add8e9d85400c78aca3832186f77f07ef14967dc81f6cc009cdae0931cc3ea65","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }}"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OTELBOT_PRIVATE_KEY"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/create-github-app-token","sha":"1b10c78c7865c340bc4f6099eb2f838309f1e8c3","version":"v3.1.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"v5.2.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":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.71.5). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Walks instrumentation modules sequentially, applying safe repository-guideline +# fixes, and opens a single PR per run once the accumulated change set reaches +# FILE_THRESHOLD modified files. Reads the list of already-reviewed modules from +# persistent repo memory and appends to it as modules are processed. +# +# Resolved workflow manifest: +# Imports: +# - .github/agents/module-cleanup.agent.md +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_CI_TRIGGER_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# - OTELBOT_PRIVATE_KEY +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 +# - gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # 50e97c2cd7a37755bbfafc9c5b7cafaece252f6e +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 +# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c +# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 +# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + +name: "Module Cleanup" +"on": + schedule: + - cron: "*/15 * * * *" + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string + +permissions: {} + +concurrency: + cancel-in-progress: false + group: module-cleanup + +run-name: "Module Cleanup" + +jobs: + activation: + needs: dispatch + if: needs.dispatch.outputs.has_work == 'true' + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }}" + GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_AGENT_VERSION: "1.0.40" + GH_AW_INFO_CLI_VERSION: "v0.71.5" + GH_AW_INFO_WORKFLOW_NAME: "Module Cleanup" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","java"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.40" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + .claude + .codex + .crush + .gemini + .opencode + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "module-cleanup.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.71.5" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES: ${{ needs.dispatch.outputs.modules }} + GH_AW_WIKI_NOTE: ${{ '' }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + + GH_AW_PROMPT_d81fb802c3cb9693_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/repo_memory_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + + Tools: create_pull_request, missing_tool, missing_data, noop + GH_AW_PROMPT_d81fb802c3cb9693_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" + cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + + GH_AW_PROMPT_d81fb802c3cb9693_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_d81fb802c3cb9693_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + + {{#runtime-import .github/agents/module-cleanup.agent.md}} + {{#runtime-import .github/workflows/module-cleanup.md}} + GH_AW_PROMPT_d81fb802c3cb9693_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES: ${{ needs.dispatch.outputs.modules }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MEMORY_BRANCH_NAME: 'memory/module-cleanup' + GH_AW_MEMORY_CONSTRAINTS: "\n\n**Constraints:**\n- **Max File Size**: 10240 bytes (0.01 MB) per file\n- **Max File Count**: 100 files per commit\n- **Max Patch Size**: 10240 bytes (10 KB) total per push (max: 100 KB)\n" + GH_AW_MEMORY_DESCRIPTION: '' + GH_AW_MEMORY_DIR: '/tmp/gh-aw/repo-memory/default/' + GH_AW_MEMORY_TARGET_REPO: ' of the current repository' + GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES: ${{ needs.dispatch.outputs.modules }} + GH_AW_WIKI_NOTE: '' + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_MEMORY_BRANCH_NAME: process.env.GH_AW_MEMORY_BRANCH_NAME, + GH_AW_MEMORY_CONSTRAINTS: process.env.GH_AW_MEMORY_CONSTRAINTS, + GH_AW_MEMORY_DESCRIPTION: process.env.GH_AW_MEMORY_DESCRIPTION, + GH_AW_MEMORY_DIR: process.env.GH_AW_MEMORY_DIR, + GH_AW_MEMORY_TARGET_REPO: process.env.GH_AW_MEMORY_TARGET_REPO, + GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES: process.env.GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES, + GH_AW_WIKI_NOTE: process.env.GH_AW_WIKI_NOTE + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: + - activation + - dispatch + if: needs.dispatch.outputs.has_work == 'true' + runs-on: ubuntu-latest + permissions: read-all + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: modulecleanup + outputs: + agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up JDK for running Gradle + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: temurin + java-version-file: .java-version + - name: Setup Gradle + uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # 50e97c2cd7a37755bbfafc9c5b7cafaece252f6e + with: + cache-read-only: true + - name: Use CLA approved bot + run: .github/scripts/use-cla-approved-bot.sh + + # Repo memory git-based storage configuration from frontmatter processed below + - name: Clone repo-memory branch (default) + env: + GH_TOKEN: ${{ github.token }} + GITHUB_SERVER_URL: ${{ github.server_url }} + BRANCH_NAME: memory/module-cleanup + TARGET_REPO: ${{ github.repository }} + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + CREATE_ORPHAN: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clone_repo_memory_branch.sh" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.40 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + - name: Generate Safe Outputs Config + run: | + 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_7b010ba9f5bed3e3_EOF' + {"create_pull_request":{"draft":false,"if_no_changes":"ignore","labels":["module cleanup"],"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"],"protected_files_policy":"allowed","title_prefix":"Module cleanup: "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_7b010ba9f5bed3e3_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"Module cleanup: \". Labels [\"module cleanup\"] will be automatically added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_pull_request": { + "defaultMax": 1, + "fields": { + "base": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "draft": { + "type": "boolean" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} + GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/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 DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -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 ghcr.io/github/gh-aw-mcpg:v0.3.6' + + 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_f52cff623c432b84_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + }, + "guard-policies": { + "allow-only": { + "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", + "repos": "$GITHUB_MCP_GUARD_REPOS" + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_f52cff623c432b84_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 60 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.40/awf-config.schema.json","network":{"allowDomains":["*.gradle-enterprise.cloud","adoptium.net","api.adoptium.net","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.foojay.io","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.apache.org","archive.ubuntu.com","azure.archive.ubuntu.com","cdn.azul.com","central.sonatype.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","develocity.apache.org","dl.google.com","dlcdn.apache.org","download.eclipse.org","download.java.net","download.oracle.com","downloads.gradle-dn.com","ge.spockframework.org","github.com","gradle.org","host.docker.internal","jcenter.bintray.com","jdk.java.net","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","maven-central.storage-download.googleapis.com","maven.apache.org","maven.google.com","maven.oracle.com","maven.pkg.github.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","plugins-artifacts.gradle.org","plugins.gradle.org","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","repo.gradle.org","repo.grails.org","repo.maven.apache.org","repo.spring.io","repo1.maven.org","repository.apache.org","s.symcb.com","s.symcd.com","scans-in.gradle.com","security.ubuntu.com","services.gradle.org","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.java.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","google/deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.40,squid=sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51,agent=sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504,api-proxy=sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280,cli-proxy=sha256:3e7152911d4b4b7b97beef9d3d7d924ff7902227e86001ef3838fb728d5d514c"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.71.5 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect Copilot errors + id: detect-copilot-errors + if: always() + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.gradle-enterprise.cloud,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.azul.com,central.sonatype.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,develocity.apache.org,dl.google.com,dlcdn.apache.org,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall 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 + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + # Upload repo memory as artifacts for push job + - name: Sanitize repo-memory filenames (default) + if: always() + continue-on-error: true + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Upload repo-memory artifact (default) + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: repo-memory-default + path: /tmp/gh-aw/repo-memory/default + retention-days: 1 + if-no-files-found: ignore + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - dispatch + - push_repo_memory + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: write + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-module-cleanup" + cancel-in-progress: false + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Generate GitHub App token + id: safe-outputs-app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ vars.OTELBOT_APP_ID }} + private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + github-api-url: ${{ github.api_url }} + permission-contents: write + permission-issues: write + permission-pull-requests: write + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Module Cleanup" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Module Cleanup" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Module Cleanup" + with: + github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Module Cleanup" + with: + github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Module Cleanup" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "module-cleanup" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} + GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} + GH_AW_SAFE_OUTPUTS_APP_TOKEN_MINTING_FAILED: ${{ needs.safe_outputs.outputs.app_token_minting_failed }} + GH_AW_CONCLUSION_APP_TOKEN_MINTING_FAILED: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_PUSH_REPO_MEMORY_RESULT: ${{ needs.push_repo_memory.result }} + GH_AW_REPO_MEMORY_VALIDATION_FAILED_default: ${{ needs.push_repo_memory.outputs.validation_failed_default }} + GH_AW_REPO_MEMORY_VALIDATION_ERROR_default: ${{ needs.push_repo_memory.outputs.validation_error_default }} + GH_AW_REPO_MEMORY_PATCH_SIZE_EXCEEDED_default: ${{ needs.push_repo_memory.outputs.patch_size_exceeded_default }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "60" + with: + github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Invalidate GitHub App token + if: always() && steps.safe-outputs-app-token.outputs.token != '' + env: + TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} + run: | + echo "Revoking GitHub App installation token..." + # GitHub CLI will auth with the token being revoked. + gh api \ + --method DELETE \ + -H "Authorization: token $TOKEN" \ + /installation/token || echo "Token revoke may already be expired." + + echo "Token invalidation step complete." + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP Config for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKFLOW_NAME: "Module Cleanup" + WORKFLOW_DESCRIPTION: "Walks instrumentation modules sequentially, applying safe repository-guideline\nfixes, and opens a single PR per run once the accumulated change set reaches\nFILE_THRESHOLD modified files. Reads the list of already-reviewed modules from\npersistent repo memory and appends to it as modules are processed." + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.40 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.40/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.40,squid=sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51,agent=sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504,api-proxy=sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280,cli-proxy=sha256:3e7152911d4b4b7b97beef9d3d7d924ff7902227e86001ef3838fb728d5d514c"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.71.5 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } + + dispatch: + if: github.repository == 'open-telemetry/opentelemetry-java-instrumentation' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + + env: + MEMORY_BRANCH: memory/module-cleanup + outputs: + has_work: ${{ steps.build-matrix.outputs.has_work }} + modules: ${{ steps.build-matrix.outputs.modules }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + - name: Fetch progress branch + run: git fetch origin "$MEMORY_BRANCH" || true + - name: Build cleanup matrix + id: build-matrix + run: | + # Files inside the repo-memory branch live at /. + progress=$(git show "origin/$MEMORY_BRANCH:$MEMORY_BRANCH/reviewed.txt" 2>/dev/null || true) + if [[ -n "$progress" ]]; then + export REVIEW_PROGRESS="$progress" + fi + python .github/scripts/module-cleanup/build-cleanup-matrix.py + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + push_repo_memory: + needs: + - activation + - agent + - detection + if: > + always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && + needs.agent.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: write + concurrency: + group: "push-repo-memory-${{ github.repository }}|memory/module-cleanup" + cancel-in-progress: false + outputs: + patch_size_exceeded_default: ${{ steps.push_repo_memory_default.outputs.patch_size_exceeded }} + validation_error_default: ${{ steps.push_repo_memory_default.outputs.validation_error }} + validation_failed_default: ${{ steps.push_repo_memory_default.outputs.validation_failed }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: . + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Download repo-memory artifact (default) + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + continue-on-error: true + with: + name: repo-memory-default + path: /tmp/gh-aw/repo-memory/default + - name: Push repo-memory changes (default) + id: push_repo_memory_default + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ github.token }} + GITHUB_RUN_ID: ${{ github.run_id }} + GITHUB_SERVER_URL: ${{ github.server_url }} + ARTIFACT_DIR: /tmp/gh-aw/repo-memory/default + MEMORY_ID: default + TARGET_REPO: ${{ github.repository }} + BRANCH_NAME: memory/module-cleanup + MAX_FILE_SIZE: 10240 + MAX_FILE_COUNT: 100 + MAX_PATCH_SIZE: 10240 + ALLOWED_EXTENSIONS: '[]' + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/push_repo_memory.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/module-cleanup" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }}" + GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_WORKFLOW_ID: "module-cleanup" + GH_AW_WORKFLOW_NAME: "Module Cleanup" + outputs: + app_token_minting_failed: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }} + created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Download patch artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Generate GitHub App token + id: safe-outputs-app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ vars.OTELBOT_APP_ID }} + private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + github-api-url: ${{ github.api_url }} + permission-contents: write + permission-issues: write + permission-pull-requests: write + - name: Checkout repository + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + token: ${{ steps.safe-outputs-app-token.outputs.token }} + persist-credentials: false + fetch-depth: 1 + - name: Configure Git credentials + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "*.gradle-enterprise.cloud,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.azul.com,central.sonatype.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,develocity.apache.org,dl.google.com,dlcdn.apache.org,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"if_no_changes\":\"ignore\",\"labels\":[\"module cleanup\"],\"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\"],\"protected_files_policy\":\"allowed\",\"title_prefix\":\"Module cleanup: \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} + GITHUB_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} + with: + github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Invalidate GitHub App token + if: always() && steps.safe-outputs-app-token.outputs.token != '' + env: + TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} + run: | + echo "Revoking GitHub App installation token..." + # GitHub CLI will auth with the token being revoked. + gh api \ + --method DELETE \ + -H "Authorization: token $TOKEN" \ + /installation/token || echo "Token revoke may already be expired." + + echo "Token invalidation step complete." + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore + diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md new file mode 100644 index 000000000000..a75332e1a37a --- /dev/null +++ b/.github/workflows/module-cleanup.md @@ -0,0 +1,208 @@ +--- +description: | + Walks instrumentation modules sequentially, applying safe repository-guideline + fixes, and opens a single PR per run once the accumulated change set reaches + FILE_THRESHOLD modified files. Reads the list of already-reviewed modules from + persistent repo memory and appends to it as modules are processed. + +on: + schedule: + - cron: "*/15 * * * *" + workflow_dispatch: + +permissions: read-all + +concurrency: + group: module-cleanup + cancel-in-progress: false + +timeout-minutes: 60 + +engine: + id: copilot + model: ${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }} + +network: + allowed: + - defaults + - java + +tools: + edit: + bash: [":*"] + repo-memory: true + +safe-outputs: + # Mint an otelbot app token so the resulting PR is authored by the + # otelbot GitHub App rather than by GITHUB_TOKEN. PRs created by + # GITHUB_TOKEN do not trigger downstream workflow runs. + github-app: + app-id: ${{ vars.OTELBOT_APP_ID }} + private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} + create-pull-request: + title-prefix: "Module cleanup: " + labels: ["module cleanup"] + draft: false + max: 1 + if-no-changes: "ignore" + # This workflow is explicitly designed to edit Gradle build files, + # gradle.properties, and similar configuration files inside + # instrumentation modules, all of which are in the default protected + # set. Disable the protected-files defense. + protected-files: allowed + +imports: + - .github/agents/module-cleanup.agent.md + +jobs: + dispatch: + if: github.repository == 'open-telemetry/opentelemetry-java-instrumentation' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + env: + # Orphan branch managed by gh-aw's `repo-memory` tool (see + # frontmatter `tools:` block). Holds `reviewed.txt`, the list of + # modules already reviewed in prior runs. The agent job mounts + # this branch automatically; the dispatch job is a separate plain + # Actions job and must fetch + read it manually. + MEMORY_BRANCH: memory/module-cleanup + outputs: + modules: ${{ steps.build-matrix.outputs.modules }} + has_work: ${{ steps.build-matrix.outputs.has_work }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + - name: Fetch progress branch + run: git fetch origin "$MEMORY_BRANCH" || true + - name: Build cleanup matrix + id: build-matrix + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Files inside the repo-memory branch live at /. + progress=$(git show "origin/$MEMORY_BRANCH:$MEMORY_BRANCH/reviewed.txt" 2>/dev/null || true) + if [[ -n "$progress" ]]; then + export REVIEW_PROGRESS="$progress" + fi + python .github/scripts/module-cleanup/build-cleanup-matrix.py + +if: ${{ needs.dispatch.outputs.has_work == 'true' }} + +steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up JDK for running Gradle + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: temurin + java-version-file: .java-version + - name: Setup Gradle + uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 + with: + cache-read-only: true + - name: Use CLA approved bot + run: .github/scripts/use-cla-approved-bot.sh +--- + +# Module Cleanup + +You walk a list of instrumentation modules sequentially, applying safe +repository-guideline fixes per module, and stop after the accumulated change +set reaches a file-count threshold so the result fits in one reasonably sized +pull request. + +## Inputs + +The dispatch job has computed which modules to walk through this run. +Read the JSON array of `{short_name, module_dir}` objects below — these are +the modules in walk order, already filtered to exclude modules that have been +reviewed in any prior run: + +``` +${{ needs.dispatch.outputs.modules }} +``` + +`FILE_THRESHOLD = 10`. Stop walking modules as soon as +`git diff --name-only origin/main | wc -l` is at least `10` after committing a +module. Do not exceed this by a wide margin — finish the current module, then +stop. + +## Persistent state + +Persistent run-to-run state lives in `/tmp/gh-aw/repo-memory/default/`. + +- `reviewed.txt` (newline-separated `` values, one per line): + modules that have already been reviewed in any prior run. The dispatch job + has already filtered the input list using this file, so the list above is + authoritative for this run. **You must append the `` of every + module you process, including modules that produced no edits, before + finishing the run.** Create the file if it does not yet exist. + +The repo-memory tool will commit and push this file automatically after the +run completes; you do not need to run any git commands against the memory +directory. + +## Per-module workflow + +For each module in the input list, in order: + +1. Record the current branch SHA: `pre=$(git rev-parse HEAD)`. +2. Invoke the `module-cleanup` persona (loaded via `imports:`) on + ``. Run the full persona end-to-end and reach its commit + step (a single commit with subject `Cleanup for `). +3. If the persona reports it had to revert all of its changes (no substantive + diff remained), reset back to `pre` (`git reset --hard "$pre"`) so the + branch state is exactly as it was before this module ran, then continue to + step 4. +4. Append `\n` to `/tmp/gh-aw/repo-memory/default/reviewed.txt`. + Do this whether or not the module produced edits — modules that produced + no edits must still be marked as reviewed so they are not re-walked on the + next run. +5. Compute `count=$(git diff --name-only origin/main | wc -l)`. If + `count >= 10`, stop the loop. Otherwise continue to the next module. + +If a tool error or unrecoverable failure prevents you from completing a +module's review, **do not** append that module to `reviewed.txt`. Reset to +`pre`, skip that module, and continue with the next one. The module will be +retried on a future run. + +## Output + +After the loop ends, your only remaining action is to emit a single +`create_pull_request` safe output with: + +- **title**: `run ${{ github.run_id }}` (the `Module cleanup: ` prefix is + prepended automatically by the safe-output framework). +- **body**: An ordered list of the modules processed in this run (using + `` and ``), followed by a per-module section + summarizing the changes applied and any unresolved items. Use the same + Markdown shape that the persona's report produces, with a top-level + `## Module: ` heading per module and a single combined + `_Module path: _` line below each heading. +- **branch**: do not specify; let the safe-output framework choose. +- **labels**: already configured in frontmatter. + +The framework will collect every commit you have made in this run, push them +to a fresh branch, and open the PR. Do not run `git push` yourself and do not +attempt to call `gh pr create`. + +If no commits were produced this run (every module reverted or the loop +terminated immediately), do not emit a `create_pull_request` output. The +`if-no-changes: ignore` configuration handles this case automatically. + +## Constraints + +- Use repository-relative paths in the PR body. +- Do not modify files in `.github/workflows/` or other infrastructure paths + unless a per-module fix strictly requires it. +- Do not run `git push`, `git fetch`, or `gh pr create`. The framework + handles all remote operations. +- Do not modify `/tmp/gh-aw/repo-memory/default/reviewed.txt` outside the + per-module append described above. +- Honor every constraint in the imported `module-cleanup` persona, including + its skip list, its auto-fix boundaries, its validation procedure, and its + prohibition on inline review comments in source files. diff --git a/.github/workflows/module-cleanup.yml b/.github/workflows/module-cleanup.yml deleted file mode 100644 index 8cde69a89e0b..000000000000 --- a/.github/workflows/module-cleanup.yml +++ /dev/null @@ -1,226 +0,0 @@ -name: Module Cleanup - -on: - schedule: - # Every 15 minutes - - cron: "*/15 * * * *" - workflow_dispatch: - -permissions: - contents: read - -# Prevent overlapping cleanup runs -concurrency: - group: module-cleanup - cancel-in-progress: false - -jobs: - # --------------------------------------------------------------------------- - # Job 1: Determine which modules to clean up - # --------------------------------------------------------------------------- - dispatch: - # Only run on official repo, not forks - if: github.repository == 'open-telemetry/opentelemetry-java-instrumentation' - runs-on: ubuntu-latest - outputs: - modules: ${{ steps.build-matrix.outputs.modules }} - has_work: ${{ steps.build-matrix.outputs.has_work }} - model: ${{ steps.model.outputs.model }} - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 1 - - - name: Fetch progress branch - run: git fetch origin otelbot/module-cleanup-progress || true - - - name: Resolve Copilot model - id: model - run: | - model=$(git show origin/otelbot/module-cleanup-progress:model.txt | xargs) - echo "model=$model" >> "$GITHUB_OUTPUT" - - - name: Build cleanup matrix - id: build-matrix - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # Read progress from the dedicated orphan branch (if it exists) - progress=$(git show origin/otelbot/module-cleanup-progress:reviewed.txt 2>/dev/null || true) - if [[ -n "$progress" ]]; then - export REVIEW_PROGRESS="$progress" - fi - python .github/scripts/module-cleanup/build-cleanup-matrix.py - - # --------------------------------------------------------------------------- - # Job 2: Walk modules sequentially on a single branch, stopping once the - # accumulated change set reaches FILE_THRESHOLD modified files. One PR per run. - # --------------------------------------------------------------------------- - cleanup: - needs: dispatch - if: needs.dispatch.outputs.has_work == 'true' - runs-on: ubuntu-latest - environment: protected - permissions: - contents: write # for git push - env: - MODULES_JSON: ${{ needs.dispatch.outputs.modules }} - MODEL: ${{ needs.dispatch.outputs.model }} - # Stop processing further modules once at least this many files have been - # modified (vs origin/main) at the end of a module. - FILE_THRESHOLD: 10 - COPILOT_ROOT: /tmp/copilot - FRAGMENTS_DIR: /tmp/pr-body-fragments - PROCESSED_MODULES: /tmp/processed-modules.txt - PR_BODY: /tmp/pr-body.md - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Fetch progress branch - run: git fetch origin otelbot/module-cleanup-progress || true - - - name: Free disk space - run: .github/scripts/gha-free-disk-space.sh - - - name: Set up JDK for running Gradle - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 - with: - distribution: temurin - java-version-file: .java-version - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 - with: - cache-read-only: true - - - name: Install Copilot CLI - run: | - curl -fsSL https://gh.io/copilot-install | bash - echo "$HOME/.local/bin" >> "$GITHUB_PATH" - - - name: Use CLA approved bot - run: .github/scripts/use-cla-approved-bot.sh - - - name: Check out cleanup branch - id: branch - run: | - branch="otelbot/module-cleanup-${GITHUB_RUN_ID}" - git checkout -B "$branch" origin/main - echo "name=$branch" >> "$GITHUB_OUTPUT" - - - name: Run Copilot cleanup loop - id: cleanup-loop - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_REVIEW_PROMPT_TEMPLATE: >- - Review all files under __MODULE_DIR__. Apply safe repository-guideline fixes directly. - Return ONLY a valid JSON object as your final answer with this exact schema: - {"summary": string, "changes": [{"path": string, "category": string, "change": string, "reason": string, "line_hint": number|null}], "unresolved": [{"path": string, "reason": string}]} - Include one changes entry for every file you changed. - Use concise factual reasons that cite the review guideline or repository rule behind each change. - In `summary`, `change`, and `reason`, use Markdown inline code backticks around code-like constructs when helpful, - including annotations, class names, method names, field names, file names, Gradle tasks, commands, flags, and config keys. - If no safe fixes were applied, still return valid JSON with an empty changes array and a brief summary. - Do not write markdown and do not wrap the JSON in code fences. - run: python .github/scripts/module-cleanup/cleanup-loop.py - - - name: Upload cleanup diagnostics artifact - if: always() - id: upload-cleanup-diagnostics - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: module-cleanup-diagnostics-${{ github.run_id }} - path: | - /tmp/copilot/** - /tmp/processed-modules.txt - if-no-files-found: ignore - - - name: Assemble PR body - if: steps.cleanup-loop.outputs.commits_on_branch != '0' - env: - ARTIFACT_URL: ${{ steps.upload-cleanup-diagnostics.outputs.artifact-url }} - run: | - set -euo pipefail - { - echo "Automated module cleanup walked the following modules in order" - echo "and stopped after accumulating at least ${FILE_THRESHOLD} modified files:" - echo - while IFS= read -r m; do - echo "- \`$m\`" - done < "$PROCESSED_MODULES" - echo - echo "---" - echo - for f in "$FRAGMENTS_DIR"/*.md; do - [[ -f "$f" ]] || continue - cat "$f" - echo - done - echo "---" - echo - echo "[Download module cleanup diagnostics]($ARTIFACT_URL)" - echo - } > "$PR_BODY" - - - name: Commit summary - if: steps.cleanup-loop.outputs.commits_on_branch != '0' - id: commit - run: | - branch="${{ steps.branch.outputs.name }}" - git push -f origin "$branch" - echo "pushed=true" >> "$GITHUB_OUTPUT" - - - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 - id: otelbot-token - if: steps.commit.outputs.pushed == 'true' - with: - app-id: ${{ vars.OTELBOT_APP_ID }} - private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} - - - name: Create PR - if: steps.commit.outputs.pushed == 'true' - env: - GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} - run: | - branch="${{ steps.branch.outputs.name }}" - title="Module cleanup (run ${GITHUB_RUN_ID})" - gh pr create \ - --title "$title" \ - --body-file "$PR_BODY" \ - --base main \ - --head "$branch" \ - --label "module cleanup" - - - name: Ensure progress branch exists - if: steps.cleanup-loop.outputs.processed_count != '0' - run: | - if ! git rev-parse --verify origin/otelbot/module-cleanup-progress >/dev/null 2>&1; then - git checkout --orphan otelbot/module-cleanup-progress - git reset --hard - git commit --allow-empty -m "Initialize progress tracking" - git push origin HEAD:otelbot/module-cleanup-progress || true - fi - - - name: Check out progress branch - if: steps.cleanup-loop.outputs.processed_count != '0' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: otelbot/module-cleanup-progress - path: progress - - - name: Mark processed modules as reviewed - if: steps.cleanup-loop.outputs.processed_count != '0' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - cd progress - - git config user.name otelbot - git config user.email 197425009+otelbot@users.noreply.github.com - - cat "$PROCESSED_MODULES" >> reviewed.txt - - git add reviewed.txt - git commit -m "Mark $(wc -l < "$PROCESSED_MODULES" | tr -d ' ') module(s) as reviewed" - git push origin HEAD:otelbot/module-cleanup-progress From 163ae89c6cd4fb9a27627911fdbf72a6185e7873 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 6 May 2026 12:04:47 -0700 Subject: [PATCH 02/16] Fork-test tweaks: workflow_dispatch only, fork repo guard, drop otelbot app --- .github/workflows/module-cleanup.lock.yml | 118 ++++++---------------- .github/workflows/module-cleanup.md | 11 +- 2 files changed, 31 insertions(+), 98 deletions(-) diff --git a/.github/workflows/module-cleanup.lock.yml b/.github/workflows/module-cleanup.lock.yml index 7bad80f88a0a..c4ebb52b55fc 100644 --- a/.github/workflows/module-cleanup.lock.yml +++ b/.github/workflows/module-cleanup.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"add8e9d85400c78aca3832186f77f07ef14967dc81f6cc009cdae0931cc3ea65","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }}"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OTELBOT_PRIVATE_KEY"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/create-github-app-token","sha":"1b10c78c7865c340bc4f6099eb2f838309f1e8c3","version":"v3.1.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"v5.2.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":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f0553f993470dfd506464c3ca24860de76a7343d02297f2530423f289ac590af","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} +# gh-aw-manifest: {"version":1,"secrets":["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/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"v5.2.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":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -37,11 +37,9 @@ # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN -# - OTELBOT_PRIVATE_KEY # # Custom actions used: # - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 -# - actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -61,8 +59,6 @@ name: "Module Cleanup" "on": - schedule: - - cron: "*/15 * * * *" workflow_dispatch: inputs: aw_context: @@ -112,7 +108,7 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }}" + GH_AW_INFO_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}" GH_AW_INFO_VERSION: "1.0.40" GH_AW_INFO_AGENT_VERSION: "1.0.40" GH_AW_INFO_CLI_VERSION: "v0.71.5" @@ -199,24 +195,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' - GH_AW_PROMPT_d81fb802c3cb9693_EOF + GH_AW_PROMPT_c0d0fd8c8f8a7986_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/repo_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_d81fb802c3cb9693_EOF + GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' - GH_AW_PROMPT_d81fb802c3cb9693_EOF + GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -245,13 +241,13 @@ jobs: {{/if}} - GH_AW_PROMPT_d81fb802c3cb9693_EOF + GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_d81fb802c3cb9693_EOF' + cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' {{#runtime-import .github/agents/module-cleanup.agent.md}} {{#runtime-import .github/workflows/module-cleanup.md}} - GH_AW_PROMPT_d81fb802c3cb9693_EOF + GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -346,8 +342,6 @@ jobs: if: needs.dispatch.outputs.has_work == 'true' runs-on: ubuntu-latest permissions: read-all - concurrency: - group: "gh-aw-copilot-${{ github.workflow }}" env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_AW_ASSETS_ALLOWED_EXTS: "" @@ -479,9 +473,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_7b010ba9f5bed3e3_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_dfeb34dc09e6d534_EOF' {"create_pull_request":{"draft":false,"if_no_changes":"ignore","labels":["module cleanup"],"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"],"protected_files_policy":"allowed","title_prefix":"Module cleanup: "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_7b010ba9f5bed3e3_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_dfeb34dc09e6d534_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -689,7 +683,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_f52cff623c432b84_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_b25309d7e54394fa_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -730,7 +724,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_f52cff623c432b84_EOF + GH_AW_MCP_CONFIG_b25309d7e54394fa_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -770,7 +764,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }} + COPILOT_MODEL: ${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -1000,18 +994,6 @@ jobs: GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.40" - - name: Generate GitHub App token - id: safe-outputs-app-token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 - with: - client-id: ${{ vars.OTELBOT_APP_ID }} - private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - repositories: ${{ github.event.repository.name }} - github-api-url: ${{ github.api_url }} - permission-contents: write - permission-issues: write - permission-pull-requests: write - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1037,7 +1019,7 @@ jobs: GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: - github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1053,7 +1035,7 @@ jobs: GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} with: - github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1067,7 +1049,7 @@ jobs: GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Module Cleanup" with: - github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1081,7 +1063,7 @@ jobs: GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Module Cleanup" with: - github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1108,8 +1090,6 @@ jobs: GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} - GH_AW_SAFE_OUTPUTS_APP_TOKEN_MINTING_FAILED: ${{ needs.safe_outputs.outputs.app_token_minting_failed }} - GH_AW_CONCLUSION_APP_TOKEN_MINTING_FAILED: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_PUSH_REPO_MEMORY_RESULT: ${{ needs.push_repo_memory.result }} @@ -1122,25 +1102,12 @@ jobs: GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "60" with: - github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Invalidate GitHub App token - if: always() && steps.safe-outputs-app-token.outputs.token != '' - env: - TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} - run: | - echo "Revoking GitHub App installation token..." - # GitHub CLI will auth with the token being revoked. - gh api \ - --method DELETE \ - -H "Authorization: token $TOKEN" \ - /installation/token || echo "Token revoke may already be expired." - - echo "Token invalidation step complete." detection: needs: @@ -1276,7 +1243,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }} + COPILOT_MODEL: ${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_VERSION: v0.71.5 @@ -1332,7 +1299,7 @@ jobs: } dispatch: - if: github.repository == 'open-telemetry/opentelemetry-java-instrumentation' + if: github.repository == 'trask/opentelemetry-java-instrumentation' runs-on: ubuntu-latest permissions: contents: read @@ -1465,12 +1432,11 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'claude-sonnet-4.6' }}" + GH_AW_ENGINE_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}" GH_AW_ENGINE_VERSION: "1.0.40" GH_AW_WORKFLOW_ID: "module-cleanup" GH_AW_WORKFLOW_NAME: "Module Cleanup" outputs: - app_token_minting_failed: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} @@ -1511,24 +1477,12 @@ jobs: with: name: agent path: /tmp/gh-aw/ - - name: Generate GitHub App token - id: safe-outputs-app-token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 - with: - client-id: ${{ vars.OTELBOT_APP_ID }} - private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - repositories: ${{ github.event.repository.name }} - github-api-url: ${{ github.api_url }} - permission-contents: write - permission-issues: write - permission-pull-requests: write - name: Checkout repository if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} - token: ${{ steps.safe-outputs-app-token.outputs.token }} + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false fetch-depth: 1 - name: Configure Git credentials @@ -1536,7 +1490,7 @@ jobs: env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} - GIT_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} + GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" @@ -1564,27 +1518,13 @@ jobs: GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"if_no_changes\":\"ignore\",\"labels\":[\"module cleanup\"],\"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\"],\"protected_files_policy\":\"allowed\",\"title_prefix\":\"Module cleanup: \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} - GITHUB_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} with: - github-token: ${{ steps.safe-outputs-app-token.outputs.token }} + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Invalidate GitHub App token - if: always() && steps.safe-outputs-app-token.outputs.token != '' - env: - TOKEN: ${{ steps.safe-outputs-app-token.outputs.token }} - run: | - echo "Revoking GitHub App installation token..." - # GitHub CLI will auth with the token being revoked. - gh api \ - --method DELETE \ - -H "Authorization: token $TOKEN" \ - /installation/token || echo "Token revoke may already be expired." - - echo "Token invalidation step complete." - name: Upload Safe Outputs Items if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md index a75332e1a37a..67475542052a 100644 --- a/.github/workflows/module-cleanup.md +++ b/.github/workflows/module-cleanup.md @@ -6,8 +6,6 @@ description: | persistent repo memory and appends to it as modules are processed. on: - schedule: - - cron: "*/15 * * * *" workflow_dispatch: permissions: read-all @@ -33,12 +31,7 @@ tools: repo-memory: true safe-outputs: - # Mint an otelbot app token so the resulting PR is authored by the - # otelbot GitHub App rather than by GITHUB_TOKEN. PRs created by - # GITHUB_TOKEN do not trigger downstream workflow runs. - github-app: - app-id: ${{ vars.OTELBOT_APP_ID }} - private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} + create-pull-request: title-prefix: "Module cleanup: " labels: ["module cleanup"] @@ -56,7 +49,7 @@ imports: jobs: dispatch: - if: github.repository == 'open-telemetry/opentelemetry-java-instrumentation' + if: github.repository == 'trask/opentelemetry-java-instrumentation' runs-on: ubuntu-latest permissions: contents: read From 898a366c4f2dd5f759c8b07f54ac90e420b5b03c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 6 May 2026 13:09:40 -0700 Subject: [PATCH 03/16] Test: disable AWF sandbox to bypass api-proxy gpt-5.5 routing issue The AWF api-proxy in firewall v0.25.40 collapses Copilot traffic to a single endpoint, which appears to break copilot-cli's /responses-API routing negotiation and blocks newer models like gpt-5.5. Disabling the agent sandbox lets copilot connect directly to api.githubcopilot.com, matching how the original (non-gh-aw) module-cleanup workflow ran successfully with gpt-5.5. --- .github/workflows/module-cleanup.lock.yml | 324 ++-------------------- .github/workflows/module-cleanup.md | 12 + 2 files changed, 42 insertions(+), 294 deletions(-) diff --git a/.github/workflows/module-cleanup.lock.yml b/.github/workflows/module-cleanup.lock.yml index c4ebb52b55fc..c74153df15f3 100644 --- a/.github/workflows/module-cleanup.lock.yml +++ b/.github/workflows/module-cleanup.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f0553f993470dfd506464c3ca24860de76a7343d02297f2530423f289ac590af","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} -# gh-aw-manifest: {"version":1,"secrets":["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/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"v5.2.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":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"09413f54cb281ca716d3a28fd9a809f1e60c14f16d74df4eb95fb20fe7848c6e","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} +# gh-aw-manifest: {"version":1,"secrets":["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/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"v5.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -44,16 +44,11 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # - github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 # - gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # 50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 -# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c # - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f @@ -117,11 +112,11 @@ jobs: GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","java"]' - GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.40" + GH_AW_INFO_FIREWALL_ENABLED: "false" + GH_AW_INFO_AWF_VERSION: "" GH_AW_INFO_AWMG_VERSION: "" - GH_AW_INFO_FIREWALL_TYPE: "squid" - GH_AW_COMPILED_STRICT: "true" + GH_AW_INFO_FIREWALL_TYPE: "" + GH_AW_COMPILED_STRICT: "false" uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -195,24 +190,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' + cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' - GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF + GH_AW_PROMPT_30bac6fc1d337821_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/repo_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' + cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF + GH_AW_PROMPT_30bac6fc1d337821_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' + cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' - GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF + GH_AW_PROMPT_30bac6fc1d337821_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' + cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -241,13 +236,13 @@ jobs: {{/if}} - GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF + GH_AW_PROMPT_30bac6fc1d337821_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF' + cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' {{#runtime-import .github/agents/module-cleanup.agent.md}} {{#runtime-import .github/workflows/module-cleanup.md}} - GH_AW_PROMPT_c0d0fd8c8f8a7986_EOF + GH_AW_PROMPT_30bac6fc1d337821_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -443,8 +438,6 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 env: GH_HOST: github.com - - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.40 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 @@ -467,15 +460,15 @@ jobs: GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - name: Generate Safe Outputs Config run: | 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_dfeb34dc09e6d534_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2b5a97188c0b960e_EOF' {"create_pull_request":{"draft":false,"if_no_changes":"ignore","labels":["module cleanup"],"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"],"protected_files_policy":"allowed","title_prefix":"Module cleanup: "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_dfeb34dc09e6d534_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_2b5a97188c0b960e_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -665,7 +658,7 @@ jobs: # Export gateway environment variables for MCP config and gateway script export MCP_GATEWAY_PORT="8080" - export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_DOMAIN="localhost" export MCP_GATEWAY_HOST_DOMAIN="localhost" MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${MCP_GATEWAY_API_KEY}" @@ -683,7 +676,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_b25309d7e54394fa_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_2701621504aa2ac4_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -704,7 +697,7 @@ jobs: }, "safeoutputs": { "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "url": "http://localhost:$GH_AW_SAFE_OUTPUTS_PORT", "headers": { "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" }, @@ -724,7 +717,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_b25309d7e54394fa_EOF + GH_AW_MCP_CONFIG_2701621504aa2ac4_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -753,13 +746,12 @@ jobs: run: | set -o pipefail touch /tmp/gh-aw/agent-step-summary.md - GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) - export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.40/awf-config.schema.json","network":{"allowDomains":["*.gradle-enterprise.cloud","adoptium.net","api.adoptium.net","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.foojay.io","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.apache.org","archive.ubuntu.com","azure.archive.ubuntu.com","cdn.azul.com","central.sonatype.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","develocity.apache.org","dl.google.com","dlcdn.apache.org","download.eclipse.org","download.java.net","download.oracle.com","downloads.gradle-dn.com","ge.spockframework.org","github.com","gradle.org","host.docker.internal","jcenter.bintray.com","jdk.java.net","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","maven-central.storage-download.googleapis.com","maven.apache.org","maven.google.com","maven.oracle.com","maven.pkg.github.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","plugins-artifacts.gradle.org","plugins.gradle.org","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","repo.gradle.org","repo.grails.org","repo.maven.apache.org","repo.spring.io","repo1.maven.org","repository.apache.org","s.symcb.com","s.symcd.com","scans-in.gradle.com","security.ubuntu.com","services.gradle.org","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.java.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","google/deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.40,squid=sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51,agent=sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504,api-proxy=sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280,cli-proxy=sha256:3e7152911d4b4b7b97beef9d3d7d924ff7902227e86001ef3838fb728d5d514c"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + mkdir -p /tmp/ + mkdir -p /tmp/gh-aw/ + mkdir -p /tmp/gh-aw/agent/ + mkdir -p /tmp/gh-aw/sandbox/agent/logs/ + GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_API_KEY: dummy-byok-key-for-offline-mode @@ -779,10 +771,6 @@ jobs: GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md GITHUB_WORKSPACE: ${{ github.workspace }} - GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_AUTHOR_NAME: github-actions[bot] - GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_COMMITTER_NAME: github-actions[bot] XDG_CONFIG_HOME: /home/runner - name: Detect Copilot errors id: detect-copilot-errors @@ -876,41 +864,6 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); await main(); - - name: Print firewall logs - if: always() - continue-on-error: true - env: - AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs - run: | - # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts - # AWF runs with sudo, creating files owned by root - sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall 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 - - name: Parse token usage for step summary - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); - await main(); - - name: Print AWF reflect summary - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); - await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -943,7 +896,6 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ - /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/pre-agent-audit.txt /tmp/gh-aw/agent/ @@ -952,17 +904,12 @@ jobs: /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch /tmp/gh-aw/aw-*.bundle - /tmp/gh-aw/awf-config.json - /tmp/gh-aw/sandbox/firewall/logs/ - /tmp/gh-aw/sandbox/firewall/audit/ - /tmp/gh-aw/sandbox/firewall/awf-reflect.json if-no-files-found: ignore conclusion: needs: - activation - agent - - detection - dispatch - push_repo_memory - safe_outputs @@ -1025,22 +972,6 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - - name: Log detection run - id: detection_runs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Module Cleanup" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} - GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); - await main(); - name: Record missing tool id: missing_tool uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1109,195 +1040,6 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - detection: - needs: - - activation - - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') - runs-on: ubuntu-latest - permissions: - contents: read - outputs: - detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} - detection_reason: ${{ steps.detection_conclusion.outputs.reason }} - detection_success: ${{ steps.detection_conclusion.outputs.success }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Download agent output artifact - id: download-agent-output - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Setup agent output environment variable - id: setup-agent-output-env - if: steps.download-agent-output.outcome == 'success' - run: | - mkdir -p /tmp/gh-aw/ - find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Checkout repository for patch context - if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - # --- Threat Detection --- - - name: Clean stale firewall files from agent artifact - run: | - rm -rf /tmp/gh-aw/sandbox/firewall/logs - rm -rf /tmp/gh-aw/sandbox/firewall/audit - - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280 ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51 - - name: Check if detection needed - id: detection_guard - if: always() - env: - OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} - HAS_PATCH: ${{ needs.agent.outputs.has_patch }} - run: | - if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then - echo "run_detection=true" >> "$GITHUB_OUTPUT" - echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" - else - echo "run_detection=false" >> "$GITHUB_OUTPUT" - echo "Detection skipped: no agent outputs or patches to analyze" - fi - - name: Clear MCP Config for detection - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f /home/runner/.copilot/mcp-config.json - rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - - name: Prepare threat detection files - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - mkdir -p /tmp/gh-aw/threat-detection/aw-prompts - cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true - cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true - for f in /tmp/gh-aw/aw-*.patch; do - [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true - done - for f in /tmp/gh-aw/aw-*.bundle; do - [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true - done - echo "Prepared threat detection files:" - ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true - - name: Setup threat detection - if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - WORKFLOW_NAME: "Module Cleanup" - WORKFLOW_DESCRIPTION: "Walks instrumentation modules sequentially, applying safe repository-guideline\nfixes, and opens a single PR per run once the accumulated change set reaches\nFILE_THRESHOLD modified files. Reads the list of already-reviewed modules from\npersistent repo memory and appends to it as modules are processed." - HAS_PATCH: ${{ needs.agent.outputs.has_patch }} - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); - await main(); - - name: Ensure threat-detection directory and log - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - mkdir -p /tmp/gh-aw/threat-detection - touch /tmp/gh-aw/threat-detection/detection.log - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '24' - package-manager-cache: false - - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 - env: - GH_HOST: github.com - - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.40 - - name: Execute GitHub Copilot CLI - if: always() && steps.detection_guard.outputs.run_detection == 'true' - continue-on-error: true - id: detection_agentic_execution - # Copilot CLI tool arguments (sorted): - timeout-minutes: 20 - run: | - set -o pipefail - touch /tmp/gh-aw/agent-step-summary.md - GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) - export GH_AW_NODE_BIN - (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.40/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.40,squid=sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51,agent=sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504,api-proxy=sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280,cli-proxy=sha256:3e7152911d4b4b7b97beef9d3d7d924ff7902227e86001ef3838fb728d5d514c"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log - env: - COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }} - GH_AW_PHASE: detection - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.71.5 - GITHUB_API_URL: ${{ github.api_url }} - GITHUB_AW: true - GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows - GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md - GITHUB_WORKSPACE: ${{ github.workspace }} - GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_AUTHOR_NAME: github-actions[bot] - GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner - - name: Upload threat detection log - if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: detection - path: /tmp/gh-aw/threat-detection/detection.log - if-no-files-found: ignore - - name: Parse and conclude threat detection - id: detection_conclusion - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} - GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" - with: - script: | - try { - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); - } catch (loadErr) { - const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; - const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); - core.error(msg); - core.setOutput('reason', 'parse_error'); - if (continueOnError) { - core.warning('\u26A0\uFE0F ' + msg); - core.setOutput('conclusion', 'warning'); - core.setOutput('success', 'false'); - } else { - core.setOutput('conclusion', 'failure'); - core.setOutput('success', 'false'); - core.setFailed(msg); - } - } - dispatch: if: github.repository == 'trask/opentelemetry-java-instrumentation' runs-on: ubuntu-latest @@ -1341,10 +1083,7 @@ jobs: needs: - activation - agent - - detection - if: > - always() && (!cancelled()) && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + if: always() && (!cancelled()) && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: contents: write @@ -1418,8 +1157,7 @@ jobs: needs: - activation - agent - - detection - if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + if: (!cancelled()) && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: contents: write @@ -1428,8 +1166,6 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/module-cleanup" - GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} - GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}" diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md index 67475542052a..1fb94db7a2c2 100644 --- a/.github/workflows/module-cleanup.md +++ b/.github/workflows/module-cleanup.md @@ -16,10 +16,20 @@ concurrency: timeout-minutes: 60 +# Disable strict mode so we can opt out of the AWF agent sandbox below. +strict: false + engine: id: copilot model: ${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }} +# Disable the AWF sandbox so copilot-cli connects directly to +# api.githubcopilot.com. The AWF api-proxy in v0.25.40 collapses Copilot +# traffic to a single endpoint, which prevents the CLI from negotiating +# /responses-API routing and blocks newer models like gpt-5.5. +sandbox: + agent: false + network: allowed: - defaults @@ -32,6 +42,8 @@ tools: safe-outputs: + threat-detection: false + create-pull-request: title-prefix: "Module cleanup: " labels: ["module cleanup"] From 39082bd7ab53d3dad8c67145edd178e728d2a9e5 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 6 May 2026 13:50:03 -0700 Subject: [PATCH 04/16] Make create_pull_request mandatory whenever commits exist --- .github/workflows/module-cleanup.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md index 1fb94db7a2c2..bf4ea9015cfd 100644 --- a/.github/workflows/module-cleanup.md +++ b/.github/workflows/module-cleanup.md @@ -177,8 +177,19 @@ retried on a future run. ## Output -After the loop ends, your only remaining action is to emit a single -`create_pull_request` safe output with: +**Before exiting, you MUST emit a single `create_pull_request` safe output +whenever any commits were produced in this run.** This applies regardless of +why the loop ended: + +- The accumulated diff reached `FILE_THRESHOLD`, OR +- You finished walking every module in the input list, OR +- You decided to stop early for any other reason. + +Emitting the `create_pull_request` is the final action of this workflow. Do +not end your turn without calling it if `git log origin/main..HEAD` shows any +commits. + +The `create_pull_request` parameters: - **title**: `run ${{ github.run_id }}` (the `Module cleanup: ` prefix is prepended automatically by the safe-output framework). From f1a99b0dcafc26dcc8b61b4481bcf7421e5d2e42 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 6 May 2026 20:03:56 -0700 Subject: [PATCH 05/16] Module cleanup: simplify finalize, drop retry loops + FORCE_MODULE --- .../module-cleanup/build-cleanup-matrix.py | 84 ++-- .../module-cleanup/export-cleanup-patch.sh | 31 ++ .github/scripts/module-cleanup/finalize.sh | 210 ++++++++ .github/workflows/module-cleanup.lock.yml | 472 +++++------------- .github/workflows/module-cleanup.md | 276 +++++----- 5 files changed, 546 insertions(+), 527 deletions(-) create mode 100644 .github/scripts/module-cleanup/export-cleanup-patch.sh create mode 100644 .github/scripts/module-cleanup/finalize.sh diff --git a/.github/scripts/module-cleanup/build-cleanup-matrix.py b/.github/scripts/module-cleanup/build-cleanup-matrix.py index 90a22fea1e30..909cb68e73c8 100644 --- a/.github/scripts/module-cleanup/build-cleanup-matrix.py +++ b/.github/scripts/module-cleanup/build-cleanup-matrix.py @@ -1,63 +1,60 @@ #!/usr/bin/env python3 -"""Build the ordered list of instrumentation modules for this review run. +"""Pick the next instrumentation module for this cleanup run. -Reads module list from settings.gradle.kts, filters out already-reviewed -modules (read from the otelbot/module-cleanup-progress branch by the workflow -and passed via REVIEW_PROGRESS), respects the open-PR cap, and writes a -`modules` JSON array + `has_work` flag to $GITHUB_OUTPUT. +Reads the module list from settings.gradle.kts, filters out already-processed +modules (passed via REVIEW_PROGRESS), and emits a single module to walk this +run plus a count of how many unprocessed modules remain after it. -The review job processes modules sequentially on a single branch, stopping -after it accumulates at least `FILE_THRESHOLD` modified files, so the list -emitted here is an upper-bound slice the job is allowed to walk through. +The workflow chains itself one module at a time. The finalize step uses +`queue_remaining` to decide whether to self-dispatch or flush the pending +queue into a PR. Environment variables: - GITHUB_OUTPUT - path to the GitHub Actions output file - GH_TOKEN - token for `gh` CLI (set automatically by the workflow) - REVIEW_PROGRESS - newline-separated list of reviewed module names - (contents of reviewed.txt on the progress branch) + GITHUB_OUTPUT - path to the GitHub Actions output file + GH_TOKEN - token for `gh` CLI (set automatically by the workflow) + REVIEW_PROGRESS - newline-separated list of processed module names + (contents of processed.txt on the memory branch, plus + shorts already in inflight module-cleanup PR bodies) + +Outputs (to $GITHUB_OUTPUT): + has_work - "true" if a module was picked, "false" otherwise + short_name - picked module's gradle short name (e.g. "akka-actor:javaagent") + module_dir - picked module's repo-relative directory + queue_remaining - count of unprocessed modules left AFTER this one """ -import json import os import re import subprocess from pathlib import Path SETTINGS_FILE = "settings.gradle.kts" -# Skip the run entirely if at least this many automated review PRs are already open. +# Skip the run entirely if at least this many module-cleanup PRs are already open. MAX_OPEN_PRS = 5 -# Upper bound on modules the review job will walk through in a single run, -# even if the file-count threshold is never reached. Keeps one run bounded. -MODULE_LIMIT_PER_RUN = 50 def parse_modules() -> list[tuple[str, str]]: """Return list of (gradle_name, module_dir) from settings.gradle.kts.""" text = Path(SETTINGS_FILE).read_text(encoding="utf-8") - # Match include(":instrumentation:activej-http:6.0:javaagent") raw = re.findall(r'include\(":instrumentation:([^"]+)"\)', text) pairs = [] for entry in sorted(raw): parts = entry.split(":") - # Skip shared/helper modules (e.g. "cdi-testing") that don't follow the - # : layout used for real instrumentation modules. if len(parts) < 2: continue module_dir = "instrumentation/" + "/".join(parts) - # Gradle module name: second-to-last:last gradle_name = f"{parts[-2]}:{parts[-1]}" pairs.append((gradle_name, module_dir)) return pairs -def load_reviewed() -> set[str]: - """Load already-reviewed module names from the REVIEW_PROGRESS env var.""" +def load_processed() -> set[str]: + """Load already-processed module names from the REVIEW_PROGRESS env var.""" progress = os.environ.get("REVIEW_PROGRESS", "") return {line.strip() for line in progress.splitlines() if line.strip()} def count_open_prs() -> int: - """Count open PRs with the module cleanup label.""" result = subprocess.run( ["gh", "pr", "list", "--label", "module cleanup", "--state", "open", "--json", "number", "--jq", "length"], @@ -67,46 +64,49 @@ def count_open_prs() -> int: def write_output(key: str, value: str) -> None: - """Append a key=value to $GITHUB_OUTPUT. Values must not contain newlines.""" assert "\n" not in value, f"multi-line $GITHUB_OUTPUT value not supported: {value!r}" with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: f.write(f"{key}={value}\n") +def emit_no_work() -> None: + write_output("has_work", "false") + write_output("short_name", "") + write_output("module_dir", "") + write_output("queue_remaining", "0") + + def main() -> None: all_modules = parse_modules() print(f"Total instrumentation modules: {len(all_modules)}") - reviewed = load_reviewed() - print(f"Already reviewed: {len(reviewed)}") + processed = load_processed() + print(f"Already processed: {len(processed)}") - remaining = [(name, d) for name, d in all_modules if name not in reviewed] + remaining = [(n, d) for n, d in all_modules if n not in processed] print(f"Remaining modules: {len(remaining)}") if not remaining: - print("All modules have been reviewed!") - write_output("has_work", "false") - write_output("modules", "[]") + print("All modules have been processed!") + emit_no_work() return open_prs = count_open_prs() - print(f"Open review PRs: {open_prs}") - + print(f"Open module-cleanup PRs: {open_prs}") if open_prs >= MAX_OPEN_PRS: print(f"PR cap reached ({open_prs} open >= {MAX_OPEN_PRS}). Skipping this cycle.") - write_output("has_work", "false") - write_output("modules", "[]") + emit_no_work() return - batch = remaining[:MODULE_LIMIT_PER_RUN] - print(f"Dispatching {len(batch)} modules (upper bound for this run)") - - modules = [{"short_name": name, "module_dir": d} for name, d in batch] - modules_json = json.dumps(modules) - print(json.dumps(modules, indent=2)) + short_name, module_dir = remaining[0] + queue_remaining = len(remaining) - 1 + print(f"Picked: {short_name} ({module_dir})") + print(f"Queue remaining after this run: {queue_remaining}") write_output("has_work", "true") - write_output("modules", modules_json) + write_output("short_name", short_name) + write_output("module_dir", module_dir) + write_output("queue_remaining", str(queue_remaining)) if __name__ == "__main__": diff --git a/.github/scripts/module-cleanup/export-cleanup-patch.sh b/.github/scripts/module-cleanup/export-cleanup-patch.sh new file mode 100644 index 000000000000..1a036b7bab59 --- /dev/null +++ b/.github/scripts/module-cleanup/export-cleanup-patch.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Final action invoked by the LLM agent: format-patch the cleanup commit +# range into /tmp/gh-aw/agent/cleanup.patch so gh-aw's auto-uploader +# includes it in the `agent` workflow artifact. The finalize job then +# downloads that artifact and applies the patch onto module-cleanup-wip. +# +# Idempotent and write-only to /tmp. Does NOT push anything. +# +# Args: +# $1 - module short_name (used for logging only) + +set -euo pipefail + +SHORT="${1:?short_name argument required}" +OUT_DIR="${OUT_DIR:-/tmp/gh-aw/agent}" +mkdir -p "$OUT_DIR" + +if ! git rev-parse --verify origin/main >/dev/null 2>&1; then + git fetch origin main --depth=1 || true +fi + +if [ -z "$(git log origin/main..HEAD --oneline 2>/dev/null || true)" ]; then + echo "No commit produced by agent for $SHORT; nothing to export." + exit 0 +fi + +# Capture every commit the persona made on top of main. The persona is +# expected to produce exactly one commit per its Phase 5 contract, but +# format-patch range-form is robust if it makes more than one. +git format-patch origin/main..HEAD --stdout > "$OUT_DIR/cleanup.patch" +echo "Wrote cleanup patch for $SHORT to $OUT_DIR/cleanup.patch" diff --git a/.github/scripts/module-cleanup/finalize.sh b/.github/scripts/module-cleanup/finalize.sh new file mode 100644 index 000000000000..9303303a61e2 --- /dev/null +++ b/.github/scripts/module-cleanup/finalize.sh @@ -0,0 +1,210 @@ +#!/bin/bash +# Finalize: single writer for both module-cleanup-wip and the +# memory/module-cleanup branch. Runs after the agent job (regardless of +# whether the agent succeeded, no-oped, or failed). +# +# Steps: +# 1. Append to memory/module-cleanup:processed.txt; if the +# agent failed, also append to failed.txt. This guarantees a failing +# module is recorded as "processed" (so it isn't retried in a loop) +# AND logged as a failure for diagnostics. +# 2. If the agent produced a cleanup patch, apply it onto +# module-cleanup-wip and push. +# 3. If wip diff vs origin/main has reached FLUSH_THRESHOLD files OR +# the queue is empty, cut a batch branch from wip, open the PR, +# and reset wip back to origin/main. +# 4. Self-dispatch the workflow unless we just opened a PR or the +# queue is empty (cron will pick up later). +# +# No rebase-retry loops on push: the workflow uses +# concurrency.group=module-cleanup with cancel-in-progress=false, so this +# job is the only writer of either branch and runs serialized across +# workflow runs. +# +# Required env: +# GH_TOKEN - token with contents:write, pull-requests:write, +# and actions:write +# GITHUB_REPOSITORY - owner/repo +# SHORT_NAME - the module short_name processed this run +# AGENT_RESULT - github.needs.agent.result ('success'|'failure'|...) +# ARTIFACT_DIR - directory of the downloaded `agent` artifact +# (may or may not contain cleanup.patch) +# QUEUE_REMAINING - count of unprocessed modules left after this one +# +# Optional env: +# FLUSH_THRESHOLD - file count that triggers a PR (default 10) +# WORKFLOW_FILE - workflow file name for self-dispatch +# MEMORY_BRANCH - default: memory/module-cleanup +# WIP_BRANCH - default: module-cleanup-wip + +set -euo pipefail + +MEMORY_BRANCH="${MEMORY_BRANCH:-memory/module-cleanup}" +WIP_BRANCH="${WIP_BRANCH:-module-cleanup-wip}" +THRESHOLD="${FLUSH_THRESHOLD:-10}" +QUEUE_REMAINING="${QUEUE_REMAINING:-0}" +REPO="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY required}" +WORKFLOW_FILE="${WORKFLOW_FILE:-module-cleanup.lock.yml}" +SHORT="${SHORT_NAME:?SHORT_NAME required}" +AGENT_RESULT="${AGENT_RESULT:-failure}" +ARTIFACT_DIR="${ARTIFACT_DIR:-./agent-artifact}" + +git fetch origin main --depth=1 +git fetch origin "$MEMORY_BRANCH" --depth=1 2>/dev/null || true +git fetch origin "$WIP_BRANCH" --depth=1 2>/dev/null || true + +# ---- 1. Update processed.txt (and failed.txt on failure) ---- + +MEM_WT=/tmp/memory-wt +rm -rf "$MEM_WT" +if git rev-parse --verify "origin/$MEMORY_BRANCH" >/dev/null 2>&1; then + git worktree add -B "$MEMORY_BRANCH" "$MEM_WT" "origin/$MEMORY_BRANCH" +else + git worktree add --orphan -B "$MEMORY_BRANCH" "$MEM_WT" + rm -rf "$MEM_WT"/* +fi + +PROCESSED="$MEM_WT/processed.txt" +FAILED="$MEM_WT/failed.txt" + +touch "$PROCESSED" +if ! grep -Fxq "$SHORT" "$PROCESSED"; then + echo "$SHORT" >> "$PROCESSED" +fi + +if [ "$AGENT_RESULT" != "success" ]; then + ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) + echo -e "$SHORT\t$ts\tagent_result=$AGENT_RESULT" >> "$FAILED" +fi + +( + cd "$MEM_WT" + git add -A + if ! git diff --cached --quiet; then + git commit -m "Mark $SHORT processed (agent_result=$AGENT_RESULT)" + git push origin "$MEMORY_BRANCH" + fi +) + +# ---- 2. Apply cleanup patch (if any) onto wip ---- + +PATCH_SRC="" +for candidate in \ + "$ARTIFACT_DIR/agent/cleanup.patch" \ + "$ARTIFACT_DIR/tmp/gh-aw/agent/cleanup.patch" \ + "$ARTIFACT_DIR/cleanup.patch"; do + if [ -f "$candidate" ]; then + PATCH_SRC="$candidate" + echo "Found cleanup patch at $candidate" + break + fi +done +if [ -z "$PATCH_SRC" ]; then + echo "No cleanup.patch (no-op or agent failed before commit)." +fi + +WIP_WT=/tmp/wip-wt +rm -rf "$WIP_WT" +if git rev-parse --verify "origin/$WIP_BRANCH" >/dev/null 2>&1; then + git worktree add -B "$WIP_BRANCH" "$WIP_WT" "origin/$WIP_BRANCH" +else + git worktree add -B "$WIP_BRANCH" "$WIP_WT" origin/main +fi + +if [ -n "$PATCH_SRC" ]; then + ( + cd "$WIP_WT" + if git am --3way "$PATCH_SRC"; then + echo "Applied cleanup for $SHORT to $WIP_BRANCH" + git push origin "$WIP_BRANCH" + else + git am --abort 2>/dev/null || true + echo "FAILED to apply cleanup for $SHORT (rebase conflict)." + ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) + ( + cd "$MEM_WT" + echo -e "$SHORT\t$ts\tgit am failed (rebase conflict)" >> "$FAILED" + git add -A + git commit -m "Record $SHORT as patch-conflict failure" + git push origin "$MEMORY_BRANCH" || true + ) + fi + ) +fi + +git fetch origin "$WIP_BRANCH" --depth=50 2>/dev/null || true + +# ---- 3. Decide flush ---- + +if git rev-parse --verify "origin/$WIP_BRANCH" >/dev/null 2>&1; then + FILE_COUNT=$(git diff --name-only origin/main "origin/$WIP_BRANCH" | wc -l) + AHEAD=$(git rev-list --count "origin/main..origin/$WIP_BRANCH") +else + FILE_COUNT=0 + AHEAD=0 +fi + +echo "wip ahead of main: $AHEAD commit(s), $FILE_COUNT file(s)" +echo "queue remaining: $QUEUE_REMAINING" +echo "threshold: $THRESHOLD" + +SHOULD_FLUSH=false +if [ "$AHEAD" -gt 0 ]; then + if [ "$FILE_COUNT" -ge "$THRESHOLD" ]; then + SHOULD_FLUSH=true + echo "Flushing: file count >= threshold." + elif [ "$QUEUE_REMAINING" -eq 0 ]; then + SHOULD_FLUSH=true + echo "Flushing: queue exhausted." + fi +fi + +OPENED_PR=false +if [ "$SHOULD_FLUSH" = "true" ]; then + RUN_ID="${GITHUB_RUN_ID:-$(date -u +%Y%m%d%H%M%S)}" + BATCH_BRANCH="module-cleanup-batch-$RUN_ID" + + git push origin "refs/remotes/origin/$WIP_BRANCH:refs/heads/$BATCH_BRANCH" + + BODY_FILE=$(mktemp) + { + echo "Automated module-cleanup batch." + echo + echo "## Modules in this batch" + echo + git -C "$WIP_WT" log "origin/main..origin/$WIP_BRANCH" \ + --reverse --format='- `%s`' \ + | sed 's|^- `Cleanup for |- `|' + echo + echo "---" + echo + git -C "$WIP_WT" log "origin/main..origin/$WIP_BRANCH" \ + --reverse --format='## %s%n%n%b%n' + } > "$BODY_FILE" + + gh pr create \ + --repo "$REPO" \ + --base main \ + --head "$BATCH_BRANCH" \ + --title "Module cleanup: batch (run $RUN_ID)" \ + --body-file "$BODY_FILE" \ + --label "module cleanup" + + git push --force origin "origin/main:refs/heads/$WIP_BRANCH" + + OPENED_PR=true +fi + +# ---- 4. Self-dispatch ---- + +if [ "$OPENED_PR" = "true" ]; then + echo "Opened a PR; cron will resume the chain on its next tick." +elif [ "$QUEUE_REMAINING" -le 0 ]; then + echo "Queue empty; nothing to dispatch." +else + echo "Self-dispatching workflow for next module." + gh workflow run "$WORKFLOW_FILE" --repo "$REPO" --ref main +fi + +git worktree remove --force "$MEM_WT" 2>/dev/null || true +git worktree remove --force "$WIP_WT" 2>/dev/null || true diff --git a/.github/workflows/module-cleanup.lock.yml b/.github/workflows/module-cleanup.lock.yml index c74153df15f3..15af563974f8 100644 --- a/.github/workflows/module-cleanup.lock.yml +++ b/.github/workflows/module-cleanup.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"09413f54cb281ca716d3a28fd9a809f1e60c14f16d74df4eb95fb20fe7848c6e","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} -# gh-aw-manifest: {"version":1,"secrets":["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/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"v5.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6825637d1a43038ad3b33f3b044b15068ade7142375b1bd3520b72bf0691eb61","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_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/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"v5.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -22,18 +22,32 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Walks instrumentation modules sequentially, applying safe repository-guideline -# fixes, and opens a single PR per run once the accumulated change set reaches -# FILE_THRESHOLD modified files. Reads the list of already-reviewed modules from -# persistent repo memory and appends to it as modules are processed. +# Walks instrumentation modules one-at-a-time, processing exactly one +# module per run. Each successful run's commit is appended to the +# `module-cleanup-wip` branch. When the wip branch reaches FILE_THRESHOLD +# modified files (or when the unprocessed-module queue empties), the +# finalize job promotes wip to a fresh batch branch, opens a PR against +# main, and resets wip back to main. Otherwise the workflow self-dispatches +# to process the next module. +# +# State: +# - `memory/module-cleanup` branch holds `processed.txt` (modules already +# attempted; never re-picked automatically) and `failed.txt` (a +# diagnostic log of timeouts and patch-conflict failures). +# - `module-cleanup-wip` branch holds the not-yet-PR'd cleanup commits. +# - Open PRs labeled `module cleanup` count toward MAX_OPEN_PRS; while at +# cap, dispatch exits and waits for cron to retry. # # Resolved workflow manifest: # Imports: # - .github/agents/module-cleanup.agent.md # +# Frontmatter env variables: +# - MODULE_DIR: (main workflow) +# - MODULE_SHORT_NAME: (main workflow) +# # Secrets used: # - COPILOT_GITHUB_TOKEN -# - GH_AW_CI_TRIGGER_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN @@ -52,8 +66,11 @@ # - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 # - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f -name: "Module Cleanup" +name: "Module Cleanup — Single Module" "on": + schedule: + - cron: "9 */6 * * *" + # Friendly format: every 6h (scattered) workflow_dispatch: inputs: aw_context: @@ -68,12 +85,14 @@ concurrency: cancel-in-progress: false group: module-cleanup -run-name: "Module Cleanup" +run-name: "Module Cleanup — Single Module" + +env: + MODULE_DIR: ${{ needs.dispatch.outputs.module_dir }} + MODULE_SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} jobs: activation: - needs: dispatch - if: needs.dispatch.outputs.has_work == 'true' runs-on: ubuntu-slim permissions: actions: read @@ -95,7 +114,7 @@ jobs: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.40" - name: Generate agentic run info @@ -107,7 +126,7 @@ jobs: GH_AW_INFO_VERSION: "1.0.40" GH_AW_INFO_AGENT_VERSION: "1.0.40" GH_AW_INFO_CLI_VERSION: "v0.71.5" - GH_AW_INFO_WORKFLOW_NAME: "Module Cleanup" + GH_AW_INFO_WORKFLOW_NAME: "Module Cleanup — Single Module" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" @@ -184,30 +203,27 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES: ${{ needs.dispatch.outputs.modules }} - GH_AW_WIKI_NOTE: ${{ '' }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' + cat << 'GH_AW_PROMPT_015450b8a9cb8dc7_EOF' - GH_AW_PROMPT_30bac6fc1d337821_EOF + GH_AW_PROMPT_015450b8a9cb8dc7_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/repo_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' + cat << 'GH_AW_PROMPT_015450b8a9cb8dc7_EOF' - Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_30bac6fc1d337821_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' + Tools: create_issue + GH_AW_PROMPT_015450b8a9cb8dc7_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" + cat << 'GH_AW_PROMPT_015450b8a9cb8dc7_EOF' - GH_AW_PROMPT_30bac6fc1d337821_EOF + GH_AW_PROMPT_015450b8a9cb8dc7_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' + cat << 'GH_AW_PROMPT_015450b8a9cb8dc7_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -236,21 +252,19 @@ jobs: {{/if}} - GH_AW_PROMPT_30bac6fc1d337821_EOF + GH_AW_PROMPT_015450b8a9cb8dc7_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_30bac6fc1d337821_EOF' + cat << 'GH_AW_PROMPT_015450b8a9cb8dc7_EOF' {{#runtime-import .github/agents/module-cleanup.agent.md}} {{#runtime-import .github/workflows/module-cleanup.md}} - GH_AW_PROMPT_30bac6fc1d337821_EOF + GH_AW_PROMPT_015450b8a9cb8dc7_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} - GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES: ${{ needs.dispatch.outputs.modules }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -270,13 +284,6 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' - GH_AW_MEMORY_BRANCH_NAME: 'memory/module-cleanup' - GH_AW_MEMORY_CONSTRAINTS: "\n\n**Constraints:**\n- **Max File Size**: 10240 bytes (0.01 MB) per file\n- **Max File Count**: 100 files per commit\n- **Max Patch Size**: 10240 bytes (10 KB) total per push (max: 100 KB)\n" - GH_AW_MEMORY_DESCRIPTION: '' - GH_AW_MEMORY_DIR: '/tmp/gh-aw/repo-memory/default/' - GH_AW_MEMORY_TARGET_REPO: ' of the current repository' - GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES: ${{ needs.dispatch.outputs.modules }} - GH_AW_WIKI_NOTE: '' with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -296,14 +303,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, - GH_AW_MEMORY_BRANCH_NAME: process.env.GH_AW_MEMORY_BRANCH_NAME, - GH_AW_MEMORY_CONSTRAINTS: process.env.GH_AW_MEMORY_CONSTRAINTS, - GH_AW_MEMORY_DESCRIPTION: process.env.GH_AW_MEMORY_DESCRIPTION, - GH_AW_MEMORY_DIR: process.env.GH_AW_MEMORY_DIR, - GH_AW_MEMORY_TARGET_REPO: process.env.GH_AW_MEMORY_TARGET_REPO, - GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES: process.env.GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULES, - GH_AW_WIKI_NOTE: process.env.GH_AW_WIKI_NOTE + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST } }); - name: Validate prompt placeholders @@ -337,6 +337,8 @@ jobs: if: needs.dispatch.outputs.has_work == 'true' runs-on: ubuntu-latest permissions: read-all + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_AW_ASSETS_ALLOWED_EXTS: "" @@ -365,7 +367,7 @@ jobs: job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.40" - name: Set runtime paths @@ -397,16 +399,6 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - # Repo memory git-based storage configuration from frontmatter processed below - - name: Clone repo-memory branch (default) - env: - GH_TOKEN: ${{ github.token }} - GITHUB_SERVER_URL: ${{ github.server_url }} - BRANCH_NAME: memory/module-cleanup - TARGET_REPO: ${{ github.repository }} - MEMORY_DIR: /tmp/gh-aw/repo-memory/default - CREATE_ORPHAN: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/clone_repo_memory_branch.sh" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} @@ -466,134 +458,53 @@ 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_2b5a97188c0b960e_EOF' - {"create_pull_request":{"draft":false,"if_no_changes":"ignore","labels":["module cleanup"],"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"],"protected_files_policy":"allowed","title_prefix":"Module cleanup: "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":10240,"max_patch_size":10240}]},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_2b5a97188c0b960e_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_8739f06f59a0367f_EOF' + {"create_issue":{"labels":["module-cleanup"],"max":1,"title_prefix":"[module-cleanup]"}} + GH_AW_SAFE_OUTPUTS_CONFIG_8739f06f59a0367f_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"Module cleanup: \". Labels [\"module cleanup\"] will be automatically added." + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[module-cleanup]\". Labels [\"module-cleanup\"] will be automatically added." }, "repo_params": {}, "dynamic_tools": [] } GH_AW_VALIDATION_JSON: | { - "create_pull_request": { + "create_issue": { "defaultMax": 1, "fields": { - "base": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, "body": { "required": true, "type": "string", "sanitize": true, "maxLength": 65000 }, - "branch": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "draft": { - "type": "boolean" - }, "labels": { "type": "array", "itemType": "string", "itemSanitize": true, "itemMaxLength": 128 }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 + "parent": { + "issueOrPRNumber": true }, - "context": { + "repo": { "type": "string", - "sanitize": true, "maxLength": 256 }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 - } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 + "temporary_id": { + "type": "string" }, - "reason": { + "title": { "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { "type": "string", "sanitize": true, "maxLength": 128 } } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - } - } - }, - "report_incomplete": { - "defaultMax": 5, - "fields": { - "details": { - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 1024 - } - } } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -676,7 +587,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_2701621504aa2ac4_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_be1221de3101d000_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -717,7 +628,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_2701621504aa2ac4_EOF + GH_AW_MCP_CONFIG_be1221de3101d000_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -742,7 +653,7 @@ jobs: - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): - timeout-minutes: 60 + timeout-minutes: 30 run: | set -o pipefail touch /tmp/gh-aw/agent-step-summary.md @@ -870,21 +781,6 @@ jobs: if [ ! -f /tmp/gh-aw/agent_output.json ]; then echo '{"items":[]}' > /tmp/gh-aw/agent_output.json fi - # Upload repo memory as artifacts for push job - - name: Sanitize repo-memory filenames (default) - if: always() - continue-on-error: true - env: - MEMORY_DIR: /tmp/gh-aw/repo-memory/default - run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - - name: Upload repo-memory artifact (default) - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: repo-memory-default - path: /tmp/gh-aw/repo-memory/default - retention-days: 1 - if-no-files-found: ignore - name: Upload agent artifacts if: always() continue-on-error: true @@ -902,8 +798,6 @@ jobs: /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json - /tmp/gh-aw/aw-*.patch - /tmp/gh-aw/aw-*.bundle if-no-files-found: ignore conclusion: @@ -911,24 +805,18 @@ jobs: - activation - agent - dispatch - - push_repo_memory + - finalize - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true') runs-on: ubuntu-slim permissions: - contents: write + contents: read issues: write - pull-requests: write concurrency: group: "gh-aw-conclusion-module-cleanup" cancel-in-progress: false - outputs: - incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} - noop_message: ${{ steps.noop.outputs.noop_message }} - tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} - total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts id: setup @@ -938,7 +826,7 @@ jobs: job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.40" - name: Download agent output artifact @@ -955,58 +843,13 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Process no-op messages - id: noop - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Module Cleanup" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); - - name: Record missing tool - id: missing_tool - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Module Cleanup" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); - await main(); - - name: Record incomplete - id: report_incomplete - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Module Cleanup" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); - await main(); - name: Handle agent failure id: handle_agent_failure if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Module Cleanup" + GH_AW_WORKFLOW_NAME: "Module Cleanup — Single Module" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "module-cleanup" @@ -1019,19 +862,13 @@ jobs: GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" - GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} - GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} - GH_AW_PUSH_REPO_MEMORY_RESULT: ${{ needs.push_repo_memory.result }} - GH_AW_REPO_MEMORY_VALIDATION_FAILED_default: ${{ needs.push_repo_memory.outputs.validation_failed_default }} - GH_AW_REPO_MEMORY_VALIDATION_ERROR_default: ${{ needs.push_repo_memory.outputs.validation_error_default }} - GH_AW_REPO_MEMORY_PATCH_SIZE_EXCEEDED_default: ${{ needs.push_repo_memory.outputs.patch_size_exceeded_default }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" - GH_AW_TIMEOUT_MINUTES: "60" + GH_AW_TIMEOUT_MINUTES: "30" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1041,17 +878,18 @@ jobs: await main(); dispatch: + needs: activation if: github.repository == 'trask/opentelemetry-java-instrumentation' runs-on: ubuntu-latest permissions: contents: read pull-requests: read - env: - MEMORY_BRANCH: memory/module-cleanup outputs: - has_work: ${{ steps.build-matrix.outputs.has_work }} - modules: ${{ steps.build-matrix.outputs.modules }} + has_work: ${{ steps.pick.outputs.has_work }} + module_dir: ${{ steps.pick.outputs.module_dir }} + queue_remaining: ${{ steps.pick.outputs.queue_remaining }} + short_name: ${{ steps.pick.outputs.short_name }} steps: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config @@ -1065,93 +903,73 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 - - name: Fetch progress branch - run: git fetch origin "$MEMORY_BRANCH" || true - - name: Build cleanup matrix - id: build-matrix + persist-credentials: false + - name: Pick next module + id: pick run: | - # Files inside the repo-memory branch live at /. - progress=$(git show "origin/$MEMORY_BRANCH:$MEMORY_BRANCH/reviewed.txt" 2>/dev/null || true) - if [[ -n "$progress" ]]; then - export REVIEW_PROGRESS="$progress" + set -euo pipefail + # processed.txt lives at the root of the memory branch. + processed="" + if git fetch origin "$MEMORY_BRANCH" --depth=1 2>/dev/null; then + processed=$(git show "origin/$MEMORY_BRANCH:processed.txt" 2>/dev/null || true) fi + # Also exclude shorts already in inflight module-cleanup PRs (their + # bodies list the modules under "## Modules in this batch" as + # `- ` + backticks + short + backticks). Once a PR merges, those + # shorts also exist in processed.txt so they won't be re-picked. + inflight=$(gh pr list --repo "$GITHUB_REPOSITORY" \ + --label "module cleanup" --state open \ + --json body --jq '.[].body' \ + | sed -n 's/^- `\([^`]*\)`$/\1/p' || true) + export REVIEW_PROGRESS="$(printf '%s\n%s\n' "$processed" "$inflight" \ + | grep -v '^$' | sort -u)" python .github/scripts/module-cleanup/build-cleanup-matrix.py env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MEMORY_BRANCH: memory/module-cleanup - push_repo_memory: + finalize: needs: - - activation - agent - if: always() && (!cancelled()) && needs.agent.result == 'success' - runs-on: ubuntu-slim + - dispatch + if: always() && needs.dispatch.outputs.has_work == 'true' + runs-on: ubuntu-latest permissions: + actions: write contents: write - concurrency: - group: "push-repo-memory-${{ github.repository }}|memory/module-cleanup" - cancel-in-progress: false - outputs: - patch_size_exceeded_default: ${{ steps.push_repo_memory_default.outputs.patch_size_exceeded }} - validation_error_default: ${{ steps.push_repo_memory_default.outputs.validation_error }} - validation_failed_default: ${{ steps.push_repo_memory_default.outputs.validation_failed }} + pull-requests: write + steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - sparse-checkout: . - - name: Configure Git credentials - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GITHUB_TOKEN: ${{ github.token }} + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - - name: Download repo-memory artifact (default) + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + persist-credentials: true + - name: Configure git author + run: .github/scripts/use-cla-approved-bot.sh + - name: Download agent artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - continue-on-error: true with: - name: repo-memory-default - path: /tmp/gh-aw/repo-memory/default - - name: Push repo-memory changes (default) - id: push_repo_memory_default - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + name: agent + path: ./agent-artifact + continue-on-error: true + - name: Finalize + run: bash .github/scripts/module-cleanup/finalize.sh env: - GH_TOKEN: ${{ github.token }} - GITHUB_RUN_ID: ${{ github.run_id }} - GITHUB_SERVER_URL: ${{ github.server_url }} - ARTIFACT_DIR: /tmp/gh-aw/repo-memory/default - MEMORY_ID: default - TARGET_REPO: ${{ github.repository }} - BRANCH_NAME: memory/module-cleanup - MAX_FILE_SIZE: 10240 - MAX_FILE_COUNT: 100 - MAX_PATCH_SIZE: 10240 - ALLOWED_EXTENSIONS: '[]' - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/push_repo_memory.cjs'); - await main(); + AGENT_RESULT: ${{ needs.agent.result }} + ARTIFACT_DIR: ./agent-artifact + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + QUEUE_REMAINING: ${{ needs.dispatch.outputs.queue_remaining }} + SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} + WORKFLOW_FILE: module-cleanup.lock.yml safe_outputs: needs: @@ -1160,9 +978,8 @@ jobs: if: (!cancelled()) && needs.agent.result != 'skipped' runs-on: ubuntu-slim permissions: - contents: write + contents: read issues: write - pull-requests: write timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/module-cleanup" @@ -1171,14 +988,14 @@ jobs: GH_AW_ENGINE_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}" GH_AW_ENGINE_VERSION: "1.0.40" GH_AW_WORKFLOW_ID: "module-cleanup" - GH_AW_WORKFLOW_NAME: "Module Cleanup" + GH_AW_WORKFLOW_NAME: "Module Cleanup — Single Module" outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }} - created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: @@ -1190,7 +1007,7 @@ jobs: job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup" + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.40" - name: Download agent output artifact @@ -1207,34 +1024,6 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Download patch artifact - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Checkout repository - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - - name: Configure Git credentials - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash @@ -1252,8 +1041,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.gradle-enterprise.cloud,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.azul.com,central.sonatype.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,develocity.apache.org,dl.google.com,dlcdn.apache.org,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"if_no_changes\":\"ignore\",\"labels\":[\"module cleanup\"],\"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\"],\"protected_files_policy\":\"allowed\",\"title_prefix\":\"Module cleanup: \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" - GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"module-cleanup\"],\"max\":1,\"title_prefix\":\"[module-cleanup]\"}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md index bf4ea9015cfd..13fcea6379e1 100644 --- a/.github/workflows/module-cleanup.md +++ b/.github/workflows/module-cleanup.md @@ -1,12 +1,28 @@ --- description: | - Walks instrumentation modules sequentially, applying safe repository-guideline - fixes, and opens a single PR per run once the accumulated change set reaches - FILE_THRESHOLD modified files. Reads the list of already-reviewed modules from - persistent repo memory and appends to it as modules are processed. + Walks instrumentation modules one-at-a-time, processing exactly one + module per run. Each successful run's commit is appended to the + `module-cleanup-wip` branch. When the wip branch reaches FILE_THRESHOLD + modified files (or when the unprocessed-module queue empties), the + finalize job promotes wip to a fresh batch branch, opens a PR against + main, and resets wip back to main. Otherwise the workflow self-dispatches + to process the next module. + + State: + - `memory/module-cleanup` branch holds `processed.txt` (modules already + attempted; never re-picked automatically) and `failed.txt` (a + diagnostic log of timeouts and patch-conflict failures). + - `module-cleanup-wip` branch holds the not-yet-PR'd cleanup commits. + - Open PRs labeled `module cleanup` count toward MAX_OPEN_PRS; while at + cap, dispatch exits and waits for cron to retry. on: workflow_dispatch: + schedule: + # Walk-driver: each cron tick starts (or resumes) the chain. Inside a + # tick, the workflow self-dispatches one module at a time until either + # a PR is opened, the open-PR cap is hit, or the queue empties. + - cron: "every 6h" permissions: read-all @@ -14,7 +30,7 @@ concurrency: group: module-cleanup cancel-in-progress: false -timeout-minutes: 60 +timeout-minutes: 30 # Disable strict mode so we can opt out of the AWF agent sandbox below. strict: false @@ -38,23 +54,11 @@ network: tools: edit: bash: [":*"] - repo-memory: true -safe-outputs: - - threat-detection: false - - create-pull-request: - title-prefix: "Module cleanup: " - labels: ["module cleanup"] - draft: false - max: 1 - if-no-changes: "ignore" - # This workflow is explicitly designed to edit Gradle build files, - # gradle.properties, and similar configuration files inside - # instrumentation modules, all of which are in the default protected - # set. Disable the protected-files defense. - protected-files: allowed +# No safe-outputs: the finalize job owns PR creation directly via `gh`, +# and memory-branch state is managed by plain git pushes from the finalize +# script. This keeps all post-LLM logic in shell where it can run reliably +# regardless of how the agent session ends. imports: - .github/agents/module-cleanup.agent.md @@ -66,36 +70,79 @@ jobs: permissions: contents: read pull-requests: read - env: - # Orphan branch managed by gh-aw's `repo-memory` tool (see - # frontmatter `tools:` block). Holds `reviewed.txt`, the list of - # modules already reviewed in prior runs. The agent job mounts - # this branch automatically; the dispatch job is a separate plain - # Actions job and must fetch + read it manually. - MEMORY_BRANCH: memory/module-cleanup outputs: - modules: ${{ steps.build-matrix.outputs.modules }} - has_work: ${{ steps.build-matrix.outputs.has_work }} + has_work: ${{ steps.pick.outputs.has_work }} + short_name: ${{ steps.pick.outputs.short_name }} + module_dir: ${{ steps.pick.outputs.module_dir }} + queue_remaining: ${{ steps.pick.outputs.queue_remaining }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 - - name: Fetch progress branch - run: git fetch origin "$MEMORY_BRANCH" || true - - name: Build cleanup matrix - id: build-matrix + persist-credentials: false + - name: Pick next module + id: pick env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MEMORY_BRANCH: memory/module-cleanup run: | - # Files inside the repo-memory branch live at /. - progress=$(git show "origin/$MEMORY_BRANCH:$MEMORY_BRANCH/reviewed.txt" 2>/dev/null || true) - if [[ -n "$progress" ]]; then - export REVIEW_PROGRESS="$progress" + set -euo pipefail + # processed.txt lives at the root of the memory branch. + processed="" + if git fetch origin "$MEMORY_BRANCH" --depth=1 2>/dev/null; then + processed=$(git show "origin/$MEMORY_BRANCH:processed.txt" 2>/dev/null || true) fi + # Also exclude shorts already in inflight module-cleanup PRs (their + # bodies list the modules under "## Modules in this batch" as + # `- ` + backticks + short + backticks). Once a PR merges, those + # shorts also exist in processed.txt so they won't be re-picked. + inflight=$(gh pr list --repo "$GITHUB_REPOSITORY" \ + --label "module cleanup" --state open \ + --json body --jq '.[].body' \ + | sed -n 's/^- `\([^`]*\)`$/\1/p' || true) + export REVIEW_PROGRESS="$(printf '%s\n%s\n' "$processed" "$inflight" \ + | grep -v '^$' | sort -u)" python .github/scripts/module-cleanup/build-cleanup-matrix.py + finalize: + needs: + - dispatch + - agent + if: always() && needs.dispatch.outputs.has_work == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + actions: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + persist-credentials: true + - name: Configure git author + run: .github/scripts/use-cla-approved-bot.sh + - name: Download agent artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: ./agent-artifact + continue-on-error: true + - name: Finalize + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} + AGENT_RESULT: ${{ needs.agent.result }} + QUEUE_REMAINING: ${{ needs.dispatch.outputs.queue_remaining }} + ARTIFACT_DIR: ./agent-artifact + WORKFLOW_FILE: module-cleanup.lock.yml + run: bash .github/scripts/module-cleanup/finalize.sh + if: ${{ needs.dispatch.outputs.has_work == 'true' }} +env: + MODULE_SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} + MODULE_DIR: ${{ needs.dispatch.outputs.module_dir }} + steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -113,112 +160,55 @@ steps: run: .github/scripts/use-cla-approved-bot.sh --- -# Module Cleanup +# Module Cleanup — Single Module -You walk a list of instrumentation modules sequentially, applying safe -repository-guideline fixes per module, and stop after the accumulated change -set reaches a file-count threshold so the result fits in one reasonably sized -pull request. +You clean up exactly **one** instrumentation module this run, then export +your commit so the finalize job can roll it into a batched PR. ## Inputs -The dispatch job has computed which modules to walk through this run. -Read the JSON array of `{short_name, module_dir}` objects below — these are -the modules in walk order, already filtered to exclude modules that have been -reviewed in any prior run: - -``` -${{ needs.dispatch.outputs.modules }} -``` - -`FILE_THRESHOLD = 10`. Stop walking modules as soon as -`git diff --name-only origin/main | wc -l` is at least `10` after committing a -module. Do not exceed this by a wide margin — finish the current module, then -stop. - -## Persistent state - -Persistent run-to-run state lives in `/tmp/gh-aw/repo-memory/default/`. - -- `reviewed.txt` (newline-separated `` values, one per line): - modules that have already been reviewed in any prior run. The dispatch job - has already filtered the input list using this file, so the list above is - authoritative for this run. **You must append the `` of every - module you process, including modules that produced no edits, before - finishing the run.** Create the file if it does not yet exist. - -The repo-memory tool will commit and push this file automatically after the -run completes; you do not need to run any git commands against the memory -directory. - -## Per-module workflow - -For each module in the input list, in order: - -1. Record the current branch SHA: `pre=$(git rev-parse HEAD)`. -2. Invoke the `module-cleanup` persona (loaded via `imports:`) on - ``. Run the full persona end-to-end and reach its commit - step (a single commit with subject `Cleanup for `). -3. If the persona reports it had to revert all of its changes (no substantive - diff remained), reset back to `pre` (`git reset --hard "$pre"`) so the - branch state is exactly as it was before this module ran, then continue to - step 4. -4. Append `\n` to `/tmp/gh-aw/repo-memory/default/reviewed.txt`. - Do this whether or not the module produced edits — modules that produced - no edits must still be marked as reviewed so they are not re-walked on the - next run. -5. Compute `count=$(git diff --name-only origin/main | wc -l)`. If - `count >= 10`, stop the loop. Otherwise continue to the next module. - -If a tool error or unrecoverable failure prevents you from completing a -module's review, **do not** append that module to `reviewed.txt`. Reset to -`pre`, skip that module, and continue with the next one. The module will be -retried on a future run. - -## Output - -**Before exiting, you MUST emit a single `create_pull_request` safe output -whenever any commits were produced in this run.** This applies regardless of -why the loop ended: - -- The accumulated diff reached `FILE_THRESHOLD`, OR -- You finished walking every module in the input list, OR -- You decided to stop early for any other reason. - -Emitting the `create_pull_request` is the final action of this workflow. Do -not end your turn without calling it if `git log origin/main..HEAD` shows any -commits. - -The `create_pull_request` parameters: - -- **title**: `run ${{ github.run_id }}` (the `Module cleanup: ` prefix is - prepended automatically by the safe-output framework). -- **body**: An ordered list of the modules processed in this run (using - `` and ``), followed by a per-module section - summarizing the changes applied and any unresolved items. Use the same - Markdown shape that the persona's report produces, with a top-level - `## Module: ` heading per module and a single combined - `_Module path: _` line below each heading. -- **branch**: do not specify; let the safe-output framework choose. -- **labels**: already configured in frontmatter. - -The framework will collect every commit you have made in this run, push them -to a fresh branch, and open the PR. Do not run `git push` yourself and do not -attempt to call `gh pr create`. - -If no commits were produced this run (every module reverted or the loop -terminated immediately), do not emit a `create_pull_request` output. The -`if-no-changes: ignore` configuration handles this case automatically. - -## Constraints - -- Use repository-relative paths in the PR body. -- Do not modify files in `.github/workflows/` or other infrastructure paths - unless a per-module fix strictly requires it. -- Do not run `git push`, `git fetch`, or `gh pr create`. The framework - handles all remote operations. -- Do not modify `/tmp/gh-aw/repo-memory/default/reviewed.txt` outside the - per-module append described above. -- Honor every constraint in the imported `module-cleanup` persona, including - its skip list, its auto-fix boundaries, its validation procedure, and its - prohibition on inline review comments in source files. +This run targets a single module. Read its identifiers from the workflow +environment, **not** from a JSON list: + +- `` is in the `MODULE_SHORT_NAME` environment variable. +- `` is in the `MODULE_DIR` environment variable. + +Use these directly via `$MODULE_SHORT_NAME` / `$MODULE_DIR` in any shell +command. Do **not** invent module names or guess directories. + +## Per-run workflow + +Run **inline in this session**. Do **not** spawn background agents, +sub-sessions, or use `Module-cleanup` as a callable tool. The persona +instructions imported into this prompt are yours; execute them yourself. + +1. Confirm the module directory exists: + `test -d "$MODULE_DIR" || { echo "Module directory missing: $MODULE_DIR"; exit 1; }` +2. Apply the imported `module-cleanup` persona's full checklist to + `$MODULE_DIR`. Reach the persona's commit step. The commit subject must + match the persona's format: `Cleanup for $MODULE_SHORT_NAME`. If the + persona reports it had to revert all of its changes (no substantive + diff remained), proceed to step 3 anyway — "no commit" is a valid + outcome and finalize handles it. +3. **Final mandatory action** (do not skip even on no-op): + ``` + bash .github/scripts/module-cleanup/export-cleanup-patch.sh "$MODULE_SHORT_NAME" + ``` + This writes `/tmp/gh-aw/agent/cleanup.patch` (a `git format-patch` of + your commit range) so gh-aw's auto-uploader includes it in the + workflow's `agent` artifact. The finalize job downloads that artifact + and applies the patch to the `module-cleanup-wip` branch. The script + is idempotent and exits cleanly with no patch if you produced no + commit. **Run it exactly once as your last action.** If you do not run + it, your work is lost. + +## What you must NOT do + +- Do not run `git push`. The finalize job handles all remote writes. +- Do not call `gh pr create`. The finalize job opens the PR. +- Do not modify `processed.txt` or `failed.txt`. The finalize job owns + the memory branch. +- Do not spawn background agents, child sessions, or sub-tasks. The + persona is loaded into this session; execute it inline. +- Do not modify files outside `$MODULE_DIR` unless the persona's + out-of-module-edit allowance applies to your specific change. From f94b42c0931323617f270b99f8e7b647013358e1 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 6 May 2026 20:05:43 -0700 Subject: [PATCH 06/16] Move MODULE env from frontmatter to GITHUB_ENV in pre-LLM step --- .github/workflows/module-cleanup.lock.yml | 58 +++++++++++------------ .github/workflows/module-cleanup.md | 11 +++-- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/.github/workflows/module-cleanup.lock.yml b/.github/workflows/module-cleanup.lock.yml index 15af563974f8..a75a59a2ec0c 100644 --- a/.github/workflows/module-cleanup.lock.yml +++ b/.github/workflows/module-cleanup.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6825637d1a43038ad3b33f3b044b15068ade7142375b1bd3520b72bf0691eb61","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_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/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"v5.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"0f80c75feda934198809b50c160ab33612f182d5a967c2582fc4ae4a1e3675a9","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"de0fac2e4500dabe0009e67214ff5f5447ce83dd"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"be666c2fcd27ec809703dec50e508c2fdc7f6654"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -42,10 +42,6 @@ # Imports: # - .github/agents/module-cleanup.agent.md # -# Frontmatter env variables: -# - MODULE_DIR: (main workflow) -# - MODULE_SHORT_NAME: (main workflow) -# # Secrets used: # - COPILOT_GITHUB_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN @@ -53,14 +49,15 @@ # - GITHUB_TOKEN # # Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 +# - actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # - github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 -# - gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # 50e97c2cd7a37755bbfafc9c5b7cafaece252f6e +# - gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # # Container images used: # - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 @@ -87,10 +84,6 @@ concurrency: run-name: "Module Cleanup — Single Module" -env: - MODULE_DIR: ${{ needs.dispatch.outputs.module_dir }} - MODULE_SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} - jobs: activation: runs-on: ubuntu-slim @@ -207,23 +200,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_015450b8a9cb8dc7_EOF' + cat << 'GH_AW_PROMPT_27c4ba7d00e54885_EOF' - GH_AW_PROMPT_015450b8a9cb8dc7_EOF + GH_AW_PROMPT_27c4ba7d00e54885_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_015450b8a9cb8dc7_EOF' + cat << 'GH_AW_PROMPT_27c4ba7d00e54885_EOF' Tools: create_issue - GH_AW_PROMPT_015450b8a9cb8dc7_EOF + GH_AW_PROMPT_27c4ba7d00e54885_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_015450b8a9cb8dc7_EOF' + cat << 'GH_AW_PROMPT_27c4ba7d00e54885_EOF' - GH_AW_PROMPT_015450b8a9cb8dc7_EOF + GH_AW_PROMPT_27c4ba7d00e54885_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_015450b8a9cb8dc7_EOF' + cat << 'GH_AW_PROMPT_27c4ba7d00e54885_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -252,13 +245,13 @@ jobs: {{/if}} - GH_AW_PROMPT_015450b8a9cb8dc7_EOF + GH_AW_PROMPT_27c4ba7d00e54885_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_015450b8a9cb8dc7_EOF' + cat << 'GH_AW_PROMPT_27c4ba7d00e54885_EOF' {{#runtime-import .github/agents/module-cleanup.agent.md}} {{#runtime-import .github/workflows/module-cleanup.md}} - GH_AW_PROMPT_015450b8a9cb8dc7_EOF + GH_AW_PROMPT_27c4ba7d00e54885_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -384,16 +377,23 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false + - env: + GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULE_DIR: ${{ needs.dispatch.outputs.module_dir }} + GH_AW_NEEDS_DISPATCH_OUTPUTS_SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} + name: Export module identifiers to env + run: | + echo "MODULE_SHORT_NAME=$GH_AW_NEEDS_DISPATCH_OUTPUTS_SHORT_NAME" >> "$GITHUB_ENV" + echo "MODULE_DIR=$GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULE_DIR" >> "$GITHUB_ENV" - name: Set up JDK for running Gradle - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 with: distribution: temurin java-version-file: .java-version - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # 50e97c2cd7a37755bbfafc9c5b7cafaece252f6e + uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e with: cache-read-only: true - name: Use CLA approved bot @@ -458,9 +458,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_8739f06f59a0367f_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ab7698892e8e1a77_EOF' {"create_issue":{"labels":["module-cleanup"],"max":1,"title_prefix":"[module-cleanup]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_8739f06f59a0367f_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_ab7698892e8e1a77_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -587,7 +587,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_be1221de3101d000_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_8e4d979586fb31bd_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -628,7 +628,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_be1221de3101d000_EOF + GH_AW_MCP_CONFIG_8e4d979586fb31bd_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md index 13fcea6379e1..a1994d7cec8a 100644 --- a/.github/workflows/module-cleanup.md +++ b/.github/workflows/module-cleanup.md @@ -139,14 +139,17 @@ jobs: if: ${{ needs.dispatch.outputs.has_work == 'true' }} -env: - MODULE_SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} - MODULE_DIR: ${{ needs.dispatch.outputs.module_dir }} - steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + - name: Export module identifiers to env + # Top-level frontmatter `env:` would land at workflow scope where + # `needs.*` is not available. Export via GITHUB_ENV instead so the + # LLM step (and its bash tool) sees MODULE_SHORT_NAME / MODULE_DIR. + run: | + echo "MODULE_SHORT_NAME=${{ needs.dispatch.outputs.short_name }}" >> "$GITHUB_ENV" + echo "MODULE_DIR=${{ needs.dispatch.outputs.module_dir }}" >> "$GITHUB_ENV" - name: Set up JDK for running Gradle uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: From 928329c1f40cc528711530e0c6600fa0fad67bfe Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 6 May 2026 20:43:57 -0700 Subject: [PATCH 07/16] Fix: absolute patch path before cd into wip worktree --- .github/scripts/module-cleanup/finalize.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/scripts/module-cleanup/finalize.sh b/.github/scripts/module-cleanup/finalize.sh index 9303303a61e2..7206cf83e8e1 100644 --- a/.github/scripts/module-cleanup/finalize.sh +++ b/.github/scripts/module-cleanup/finalize.sh @@ -94,8 +94,9 @@ for candidate in \ "$ARTIFACT_DIR/tmp/gh-aw/agent/cleanup.patch" \ "$ARTIFACT_DIR/cleanup.patch"; do if [ -f "$candidate" ]; then - PATCH_SRC="$candidate" - echo "Found cleanup patch at $candidate" + # Absolute path so the value survives the cd into $WIP_WT below. + PATCH_SRC="$(realpath "$candidate")" + echo "Found cleanup patch at $PATCH_SRC" break fi done From 836e47a0acdf8da86f3f835d3c189859e2446d2b Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 7 May 2026 07:53:19 -0700 Subject: [PATCH 08/16] finalize: don't abort on gh pr create failure --- .github/scripts/module-cleanup/finalize.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/scripts/module-cleanup/finalize.sh b/.github/scripts/module-cleanup/finalize.sh index 7206cf83e8e1..9e56c50b64f0 100644 --- a/.github/scripts/module-cleanup/finalize.sh +++ b/.github/scripts/module-cleanup/finalize.sh @@ -183,13 +183,18 @@ if [ "$SHOULD_FLUSH" = "true" ]; then --reverse --format='## %s%n%n%b%n' } > "$BODY_FILE" - gh pr create \ + # Don't let a transient `gh pr create` failure (e.g. label not found, + # rate limiting) abort before we reset wip and self-dispatch — the batch + # branch is already pushed and a PR can be opened manually. + if ! gh pr create \ --repo "$REPO" \ --base main \ --head "$BATCH_BRANCH" \ --title "Module cleanup: batch (run $RUN_ID)" \ --body-file "$BODY_FILE" \ - --label "module cleanup" + --label "module cleanup"; then + echo "WARNING: gh pr create failed; batch branch $BATCH_BRANCH still pushed." + fi git push --force origin "origin/main:refs/heads/$WIP_BRANCH" From ddd36e538bce292f8a7210aa1056602ae6310010 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 7 May 2026 08:36:13 -0700 Subject: [PATCH 09/16] Per-chain wip branch (module-cleanup-wip-) --- .github/scripts/module-cleanup/finalize.sh | 29 +- .github/workflows/module-cleanup.invalid.yml | 1084 ++++++++++++++++++ .github/workflows/module-cleanup.lock.yml | 68 +- .github/workflows/module-cleanup.md | 39 +- 4 files changed, 1175 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/module-cleanup.invalid.yml diff --git a/.github/scripts/module-cleanup/finalize.sh b/.github/scripts/module-cleanup/finalize.sh index 9e56c50b64f0..1eb9f96459b4 100644 --- a/.github/scripts/module-cleanup/finalize.sh +++ b/.github/scripts/module-cleanup/finalize.sh @@ -11,10 +11,12 @@ # 2. If the agent produced a cleanup patch, apply it onto # module-cleanup-wip and push. # 3. If wip diff vs origin/main has reached FLUSH_THRESHOLD files OR -# the queue is empty, cut a batch branch from wip, open the PR, -# and reset wip back to origin/main. +# the queue is empty, cut a batch branch from wip and open the PR. +# Wip is NOT reset — each chain has its own wip branch, so the next +# chain starts fresh on a different wip. # 4. Self-dispatch the workflow unless we just opened a PR or the -# queue is empty (cron will pick up later). +# queue is empty (cron will pick up later). Threads WIP_BRANCH so +# the next run in the chain reuses the same wip. # # No rebase-retry loops on push: the workflow uses # concurrency.group=module-cleanup with cancel-in-progress=false, so this @@ -35,12 +37,13 @@ # FLUSH_THRESHOLD - file count that triggers a PR (default 10) # WORKFLOW_FILE - workflow file name for self-dispatch # MEMORY_BRANCH - default: memory/module-cleanup -# WIP_BRANCH - default: module-cleanup-wip +# WIP_BRANCH - per-chain wip branch (passed in by the workflow; +# defaults to module-cleanup-wip-) set -euo pipefail MEMORY_BRANCH="${MEMORY_BRANCH:-memory/module-cleanup}" -WIP_BRANCH="${WIP_BRANCH:-module-cleanup-wip}" +WIP_BRANCH="${WIP_BRANCH:-module-cleanup-wip-${GITHUB_RUN_ID:-manual}}" THRESHOLD="${FLUSH_THRESHOLD:-10}" QUEUE_REMAINING="${QUEUE_REMAINING:-0}" REPO="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY required}" @@ -183,20 +186,13 @@ if [ "$SHOULD_FLUSH" = "true" ]; then --reverse --format='## %s%n%n%b%n' } > "$BODY_FILE" - # Don't let a transient `gh pr create` failure (e.g. label not found, - # rate limiting) abort before we reset wip and self-dispatch — the batch - # branch is already pushed and a PR can be opened manually. - if ! gh pr create \ + gh pr create \ --repo "$REPO" \ --base main \ --head "$BATCH_BRANCH" \ --title "Module cleanup: batch (run $RUN_ID)" \ --body-file "$BODY_FILE" \ - --label "module cleanup"; then - echo "WARNING: gh pr create failed; batch branch $BATCH_BRANCH still pushed." - fi - - git push --force origin "origin/main:refs/heads/$WIP_BRANCH" + --label "module cleanup" OPENED_PR=true fi @@ -208,8 +204,9 @@ if [ "$OPENED_PR" = "true" ]; then elif [ "$QUEUE_REMAINING" -le 0 ]; then echo "Queue empty; nothing to dispatch." else - echo "Self-dispatching workflow for next module." - gh workflow run "$WORKFLOW_FILE" --repo "$REPO" --ref main + echo "Self-dispatching workflow for next module on $WIP_BRANCH." + gh workflow run "$WORKFLOW_FILE" --repo "$REPO" --ref main \ + --field "wip_branch=$WIP_BRANCH" fi git worktree remove --force "$MEM_WT" 2>/dev/null || true diff --git a/.github/workflows/module-cleanup.invalid.yml b/.github/workflows/module-cleanup.invalid.yml new file mode 100644 index 000000000000..e67fe049e9cd --- /dev/null +++ b/.github/workflows/module-cleanup.invalid.yml @@ -0,0 +1,1084 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5dabc026c51d548b320ce3c4803058fc9258c100f6d9358eedd266cbda5a054c","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"de0fac2e4500dabe0009e67214ff5f5447ce83dd"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"be666c2fcd27ec809703dec50e508c2fdc7f6654"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.71.5). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Walks instrumentation modules one-at-a-time, processing exactly one +# module per run. Each successful run's commit is appended to a +# per-chain `module-cleanup-wip-` branch. When the wip branch +# reaches FILE_THRESHOLD modified files (or when the unprocessed-module +# queue empties), the finalize job promotes wip to a `module-cleanup-batch-` +# branch and opens a PR against main. Otherwise the workflow self-dispatches +# to process the next module, threading the same wip branch. +# +# Each chain (cron tick or manual workflow_dispatch) gets its own wip +# branch named after the first run's id. If a chain dies mid-flight (e.g. +# PR creation fails), its wip is simply abandoned — the next cron tick +# starts a fresh chain on a fresh wip. Old wip and batch branches can be +# garbage-collected manually. +# +# State: +# - `memory/module-cleanup` branch holds `processed.txt` (modules already +# attempted; never re-picked automatically) and `failed.txt` (a +# diagnostic log of timeouts and patch-conflict failures). +# - `module-cleanup-wip-` branches hold not-yet-PR'd commits +# for an in-flight chain. +# - Open PRs labeled `module cleanup` count toward MAX_OPEN_PRS; while at +# cap, dispatch exits and waits for cron to retry. +# +# Resolved workflow manifest: +# Imports: +# - .github/agents/module-cleanup.agent.md +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 +# - gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e +# +# Container images used: +# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 +# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + +name: "Module Cleanup — Single Module" +"on": + schedule: + - cron: "9 */6 * * *" + # Friendly format: every 6h (scattered) + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string + wip_branch: + description: Per-chain wip branch (set automatically by self-dispatch; leave blank for manual chains). + required: false + type: string + +permissions: {} + +concurrency: + cancel-in-progress: false + group: module-cleanup + +run-name: "Module Cleanup — Single Module" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}" + GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_AGENT_VERSION: "1.0.40" + GH_AW_INFO_CLI_VERSION: "v0.71.5" + GH_AW_INFO_WORKFLOW_NAME: "Module Cleanup — Single Module" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","java"]' + GH_AW_INFO_FIREWALL_ENABLED: "false" + GH_AW_INFO_AWF_VERSION: "" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "" + GH_AW_COMPILED_STRICT: "false" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + .claude + .codex + .crush + .gemini + .opencode + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "module-cleanup.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.71.5" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_919470441c431f89_EOF' + + GH_AW_PROMPT_919470441c431f89_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_919470441c431f89_EOF' + + Tools: create_issue + GH_AW_PROMPT_919470441c431f89_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" + cat << 'GH_AW_PROMPT_919470441c431f89_EOF' + + GH_AW_PROMPT_919470441c431f89_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_919470441c431f89_EOF' + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_919470441c431f89_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_919470441c431f89_EOF' + + {{#runtime-import .github/agents/module-cleanup.agent.md}} + {{#runtime-import .github/workflows/module-cleanup.md}} + GH_AW_PROMPT_919470441c431f89_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: + - activation + - dispatch + if: needs.dispatch.outputs.has_work == 'true' + runs-on: ubuntu-latest + permissions: read-all + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: modulecleanup + outputs: + agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + - env: + GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULE_DIR: ${{ needs.dispatch.outputs.module_dir }} + GH_AW_NEEDS_DISPATCH_OUTPUTS_SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} + name: Export module identifiers to env + run: | + echo "MODULE_SHORT_NAME=$GH_AW_NEEDS_DISPATCH_OUTPUTS_SHORT_NAME" >> "$GITHUB_ENV" + echo "MODULE_DIR=$GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULE_DIR" >> "$GITHUB_ENV" + - name: Set up JDK for running Gradle + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 + with: + distribution: temurin + java-version-file: .java-version + - name: Setup Gradle + uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e + with: + cache-read-only: true + - name: Use CLA approved bot + run: .github/scripts/use-cla-approved-bot.sh + + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + env: + GH_HOST: github.com + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + - name: Generate Safe Outputs Config + run: | + 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_f1f8f9950fbf9e63_EOF' + {"create_issue":{"labels":["module-cleanup"],"max":1,"title_prefix":"[module-cleanup]"}} + GH_AW_SAFE_OUTPUTS_CONFIG_f1f8f9950fbf9e63_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[module-cleanup]\". Labels [\"module-cleanup\"] will be automatically added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} + GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="localhost" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/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 DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -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 ghcr.io/github/gh-aw-mcpg:v0.3.6' + + 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_878d333d60bd5afa_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + }, + "guard-policies": { + "allow-only": { + "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", + "repos": "$GITHUB_MCP_GUARD_REPOS" + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://localhost:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_878d333d60bd5afa_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 30 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + mkdir -p /tmp/ + mkdir -p /tmp/gh-aw/ + mkdir -p /tmp/gh-aw/agent/ + mkdir -p /tmp/gh-aw/sandbox/agent/logs/ + GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.71.5 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Detect Copilot errors + id: detect-copilot-errors + if: always() + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.gradle-enterprise.cloud,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.azul.com,central.sonatype.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,develocity.apache.org,dl.google.com,dlcdn.apache.org,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - dispatch + - finalize + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + concurrency: + group: "gh-aw-conclusion-module-cleanup" + cancel-in-progress: false + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Module Cleanup — Single Module" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "module-cleanup" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "30" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + dispatch: + needs: activation + if: github.repository == 'trask/opentelemetry-java-instrumentation' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + + outputs: + has_work: ${{ steps.pick.outputs.has_work }} + module_dir: ${{ steps.pick.outputs.module_dir }} + queue_remaining: ${{ steps.pick.outputs.queue_remaining }} + short_name: ${{ steps.pick.outputs.short_name }} + wip_branch: ${{ steps.wip.outputs.wip_branch }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + persist-credentials: false + - name: Resolve wip branch for this chain + id: wip + run: | + set -euo pipefail + inherited='${{ inputs.wip_branch }}' + if [ -n "$inherited" ]; then + echo "wip_branch=$inherited" >> "$GITHUB_OUTPUT" + else + echo "wip_branch=module-cleanup-wip-${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT" + fi + - name: Pick next module + id: pick + run: | + set -euo pipefail + # processed.txt lives at the root of the memory branch. + processed="" + if git fetch origin "$MEMORY_BRANCH" --depth=1 2>/dev/null; then + processed=$(git show "origin/$MEMORY_BRANCH:processed.txt" 2>/dev/null || true) + fi + # Also exclude shorts already in inflight module-cleanup PRs (their + # bodies list the modules under "## Modules in this batch" as + # `- ` + backticks + short + backticks). Once a PR merges, those + # shorts also exist in processed.txt so they won't be re-picked. + inflight=$(gh pr list --repo "$GITHUB_REPOSITORY" \ + --label "module cleanup" --state open \ + --json body --jq '.[].body' \ + | sed -n 's/^- `\([^`]*\)`$/\1/p' || true) + export REVIEW_PROGRESS="$(printf '%s\n%s\n' "$processed" "$inflight" \ + | grep -v '^$' | sort -u)" + python .github/scripts/module-cleanup/build-cleanup-matrix.py + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MEMORY_BRANCH: memory/module-cleanup + + finalize: + needs: + - agent + - dispatch + if: always() && needs.dispatch.outputs.has_work == 'true' + runs-on: ubuntu-latest + permissions: + actions: write + contents: write + pull-requests: write + + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + persist-credentials: true + - name: Configure git author + run: .github/scripts/use-cla-approved-bot.sh + - name: Download agent artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: ./agent-artifact + continue-on-error: true + - name: Finalize + run: bash .github/scripts/module-cleanup/finalize.sh + env: + AGENT_RESULT: ${{ needs.agent.result }} + ARTIFACT_DIR: ./agent-artifact + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + QUEUE_REMAINING: ${{ needs.dispatch.outputs.queue_remaining }} + SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} + WIP_BRANCH: ${{ needs.dispatch.outputs.wip_branch }} + WORKFLOW_FILE: module-cleanup.lock.yml + + safe_outputs: + needs: + - activation + - agent + if: (!cancelled()) && needs.agent.result != 'skipped' + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/module-cleanup" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}" + GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_WORKFLOW_ID: "module-cleanup" + GH_AW_WORKFLOW_NAME: "Module Cleanup — Single Module" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "*.gradle-enterprise.cloud,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.azul.com,central.sonatype.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,develocity.apache.org,dl.google.com,dlcdn.apache.org,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"module-cleanup\"],\"max\":1,\"title_prefix\":\"[module-cleanup]\"}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore + diff --git a/.github/workflows/module-cleanup.lock.yml b/.github/workflows/module-cleanup.lock.yml index a75a59a2ec0c..f9f6140e42b1 100644 --- a/.github/workflows/module-cleanup.lock.yml +++ b/.github/workflows/module-cleanup.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"0f80c75feda934198809b50c160ab33612f182d5a967c2582fc4ae4a1e3675a9","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f6c05f53ffb41e5272ece56e2c067d880893c6b8eba67bca59045f31bad0c498","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"de0fac2e4500dabe0009e67214ff5f5447ce83dd"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"be666c2fcd27ec809703dec50e508c2fdc7f6654"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -23,18 +23,25 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # Walks instrumentation modules one-at-a-time, processing exactly one -# module per run. Each successful run's commit is appended to the -# `module-cleanup-wip` branch. When the wip branch reaches FILE_THRESHOLD -# modified files (or when the unprocessed-module queue empties), the -# finalize job promotes wip to a fresh batch branch, opens a PR against -# main, and resets wip back to main. Otherwise the workflow self-dispatches -# to process the next module. +# module per run. Each successful run's commit is appended to a +# per-chain `module-cleanup-wip-` branch. When the wip branch +# reaches FILE_THRESHOLD modified files (or when the unprocessed-module +# queue empties), the finalize job promotes wip to a `module-cleanup-batch-` +# branch and opens a PR against main. Otherwise the workflow self-dispatches +# to process the next module, threading the same wip branch. +# +# Each chain (cron tick or manual workflow_dispatch) gets its own wip +# branch named after the first run's id. If a chain dies mid-flight (e.g. +# PR creation fails), its wip is simply abandoned — the next cron tick +# starts a fresh chain on a fresh wip. Old wip and batch branches can be +# garbage-collected manually. # # State: # - `memory/module-cleanup` branch holds `processed.txt` (modules already # attempted; never re-picked automatically) and `failed.txt` (a # diagnostic log of timeouts and patch-conflict failures). -# - `module-cleanup-wip` branch holds the not-yet-PR'd cleanup commits. +# - `module-cleanup-wip-` branches hold not-yet-PR'd commits +# for an in-flight chain. # - Open PRs labeled `module cleanup` count toward MAX_OPEN_PRS; while at # cap, dispatch exits and waits for cron to retry. # @@ -75,6 +82,10 @@ name: "Module Cleanup — Single Module" description: Agent caller context (used internally by Agentic Workflows). required: false type: string + wip_branch: + description: Per-chain wip branch (set automatically by self-dispatch; leave blank for manual chains). + required: false + type: string permissions: {} @@ -200,23 +211,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_27c4ba7d00e54885_EOF' + cat << 'GH_AW_PROMPT_5c26254bd8ba649f_EOF' - GH_AW_PROMPT_27c4ba7d00e54885_EOF + GH_AW_PROMPT_5c26254bd8ba649f_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_27c4ba7d00e54885_EOF' + cat << 'GH_AW_PROMPT_5c26254bd8ba649f_EOF' Tools: create_issue - GH_AW_PROMPT_27c4ba7d00e54885_EOF + GH_AW_PROMPT_5c26254bd8ba649f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_27c4ba7d00e54885_EOF' + cat << 'GH_AW_PROMPT_5c26254bd8ba649f_EOF' - GH_AW_PROMPT_27c4ba7d00e54885_EOF + GH_AW_PROMPT_5c26254bd8ba649f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_27c4ba7d00e54885_EOF' + cat << 'GH_AW_PROMPT_5c26254bd8ba649f_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -245,13 +256,13 @@ jobs: {{/if}} - GH_AW_PROMPT_27c4ba7d00e54885_EOF + GH_AW_PROMPT_5c26254bd8ba649f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_27c4ba7d00e54885_EOF' + cat << 'GH_AW_PROMPT_5c26254bd8ba649f_EOF' {{#runtime-import .github/agents/module-cleanup.agent.md}} {{#runtime-import .github/workflows/module-cleanup.md}} - GH_AW_PROMPT_27c4ba7d00e54885_EOF + GH_AW_PROMPT_5c26254bd8ba649f_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -458,9 +469,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_ab7698892e8e1a77_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_1a392919af0c2cbe_EOF' {"create_issue":{"labels":["module-cleanup"],"max":1,"title_prefix":"[module-cleanup]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_ab7698892e8e1a77_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_1a392919af0c2cbe_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -587,7 +598,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_8e4d979586fb31bd_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_95d6aefe8340e57d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -628,7 +639,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_8e4d979586fb31bd_EOF + GH_AW_MCP_CONFIG_95d6aefe8340e57d_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -890,6 +901,7 @@ jobs: module_dir: ${{ steps.pick.outputs.module_dir }} queue_remaining: ${{ steps.pick.outputs.queue_remaining }} short_name: ${{ steps.pick.outputs.short_name }} + wip_branch: ${{ steps.wip.outputs.wip_branch }} steps: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config @@ -904,6 +916,17 @@ jobs: with: fetch-depth: 1 persist-credentials: false + - name: Resolve wip branch for this chain + id: wip + run: | + set -euo pipefail + if [ -n "$INHERITED_WIP" ]; then + echo "wip_branch=$INHERITED_WIP" >> "$GITHUB_OUTPUT" + else + echo "wip_branch=module-cleanup-wip-${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT" + fi + env: + INHERITED_WIP: ${{ inputs.wip_branch }} - name: Pick next module id: pick run: | @@ -969,6 +992,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} QUEUE_REMAINING: ${{ needs.dispatch.outputs.queue_remaining }} SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} + WIP_BRANCH: ${{ needs.dispatch.outputs.wip_branch }} WORKFLOW_FILE: module-cleanup.lock.yml safe_outputs: diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md index a1994d7cec8a..d8beb667ad28 100644 --- a/.github/workflows/module-cleanup.md +++ b/.github/workflows/module-cleanup.md @@ -1,23 +1,35 @@ --- description: | Walks instrumentation modules one-at-a-time, processing exactly one - module per run. Each successful run's commit is appended to the - `module-cleanup-wip` branch. When the wip branch reaches FILE_THRESHOLD - modified files (or when the unprocessed-module queue empties), the - finalize job promotes wip to a fresh batch branch, opens a PR against - main, and resets wip back to main. Otherwise the workflow self-dispatches - to process the next module. + module per run. Each successful run's commit is appended to a + per-chain `module-cleanup-wip-` branch. When the wip branch + reaches FILE_THRESHOLD modified files (or when the unprocessed-module + queue empties), the finalize job promotes wip to a `module-cleanup-batch-` + branch and opens a PR against main. Otherwise the workflow self-dispatches + to process the next module, threading the same wip branch. + + Each chain (cron tick or manual workflow_dispatch) gets its own wip + branch named after the first run's id. If a chain dies mid-flight (e.g. + PR creation fails), its wip is simply abandoned — the next cron tick + starts a fresh chain on a fresh wip. Old wip and batch branches can be + garbage-collected manually. State: - `memory/module-cleanup` branch holds `processed.txt` (modules already attempted; never re-picked automatically) and `failed.txt` (a diagnostic log of timeouts and patch-conflict failures). - - `module-cleanup-wip` branch holds the not-yet-PR'd cleanup commits. + - `module-cleanup-wip-` branches hold not-yet-PR'd commits + for an in-flight chain. - Open PRs labeled `module cleanup` count toward MAX_OPEN_PRS; while at cap, dispatch exits and waits for cron to retry. on: workflow_dispatch: + inputs: + wip_branch: + description: "Per-chain wip branch (set automatically by self-dispatch; leave blank for manual chains)." + required: false + type: string schedule: # Walk-driver: each cron tick starts (or resumes) the chain. Inside a # tick, the workflow self-dispatches one module at a time until either @@ -75,11 +87,23 @@ jobs: short_name: ${{ steps.pick.outputs.short_name }} module_dir: ${{ steps.pick.outputs.module_dir }} queue_remaining: ${{ steps.pick.outputs.queue_remaining }} + wip_branch: ${{ steps.wip.outputs.wip_branch }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false + - name: Resolve wip branch for this chain + id: wip + env: + INHERITED_WIP: ${{ inputs.wip_branch }} + run: | + set -euo pipefail + if [ -n "$INHERITED_WIP" ]; then + echo "wip_branch=$INHERITED_WIP" >> "$GITHUB_OUTPUT" + else + echo "wip_branch=module-cleanup-wip-${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT" + fi - name: Pick next module id: pick env: @@ -133,6 +157,7 @@ jobs: SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} AGENT_RESULT: ${{ needs.agent.result }} QUEUE_REMAINING: ${{ needs.dispatch.outputs.queue_remaining }} + WIP_BRANCH: ${{ needs.dispatch.outputs.wip_branch }} ARTIFACT_DIR: ./agent-artifact WORKFLOW_FILE: module-cleanup.lock.yml run: bash .github/scripts/module-cleanup/finalize.sh From 434666a745769538d12fc0811966b2d655d30001 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 7 May 2026 08:36:24 -0700 Subject: [PATCH 10/16] Remove stray invalid.yml artifact --- .github/workflows/module-cleanup.invalid.yml | 1084 ------------------ 1 file changed, 1084 deletions(-) delete mode 100644 .github/workflows/module-cleanup.invalid.yml diff --git a/.github/workflows/module-cleanup.invalid.yml b/.github/workflows/module-cleanup.invalid.yml deleted file mode 100644 index e67fe049e9cd..000000000000 --- a/.github/workflows/module-cleanup.invalid.yml +++ /dev/null @@ -1,1084 +0,0 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5dabc026c51d548b320ce3c4803058fc9258c100f6d9358eedd266cbda5a054c","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"de0fac2e4500dabe0009e67214ff5f5447ce83dd"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"be666c2fcd27ec809703dec50e508c2fdc7f6654"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ -# | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ -# \_| |_/\__, |\___|_| |_|\__|_|\___| -# __/ | -# _ _ |___/ -# | | | | / _| | -# | | | | ___ _ __ _ __| |_| | _____ ____ -# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| -# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ -# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ -# -# This file was automatically generated by gh-aw (v0.71.5). DO NOT EDIT. -# -# To update this file, edit the corresponding .md file and run: -# gh aw compile -# Not all edits will cause changes to this file. -# -# For more information: https://github.github.com/gh-aw/introduction/overview/ -# -# Walks instrumentation modules one-at-a-time, processing exactly one -# module per run. Each successful run's commit is appended to a -# per-chain `module-cleanup-wip-` branch. When the wip branch -# reaches FILE_THRESHOLD modified files (or when the unprocessed-module -# queue empties), the finalize job promotes wip to a `module-cleanup-batch-` -# branch and opens a PR against main. Otherwise the workflow self-dispatches -# to process the next module, threading the same wip branch. -# -# Each chain (cron tick or manual workflow_dispatch) gets its own wip -# branch named after the first run's id. If a chain dies mid-flight (e.g. -# PR creation fails), its wip is simply abandoned — the next cron tick -# starts a fresh chain on a fresh wip. Old wip and batch branches can be -# garbage-collected manually. -# -# State: -# - `memory/module-cleanup` branch holds `processed.txt` (modules already -# attempted; never re-picked automatically) and `failed.txt` (a -# diagnostic log of timeouts and patch-conflict failures). -# - `module-cleanup-wip-` branches hold not-yet-PR'd commits -# for an in-flight chain. -# - Open PRs labeled `module cleanup` count toward MAX_OPEN_PRS; while at -# cap, dispatch exits and waits for cron to retry. -# -# Resolved workflow manifest: -# Imports: -# - .github/agents/module-cleanup.agent.md -# -# Secrets used: -# - COPILOT_GITHUB_TOKEN -# - GH_AW_GITHUB_MCP_SERVER_TOKEN -# - GH_AW_GITHUB_TOKEN -# - GITHUB_TOKEN -# -# Custom actions used: -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 -# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 -# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 -# - gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e -# -# Container images used: -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 -# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - -name: "Module Cleanup — Single Module" -"on": - schedule: - - cron: "9 */6 * * *" - # Friendly format: every 6h (scattered) - workflow_dispatch: - inputs: - aw_context: - default: "" - description: Agent caller context (used internally by Agentic Workflows). - required: false - type: string - wip_branch: - description: Per-chain wip branch (set automatically by self-dispatch; leave blank for manual chains). - required: false - type: string - -permissions: {} - -concurrency: - cancel-in-progress: false - group: module-cleanup - -run-name: "Module Cleanup — Single Module" - -jobs: - activation: - runs-on: ubuntu-slim - permissions: - actions: read - contents: read - outputs: - comment_id: "" - comment_repo: "" - engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} - lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} - model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} - setup-trace-id: ${{ steps.setup.outputs.trace-id }} - stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Generate agentic run info - id: generate_aw_info - env: - GH_AW_INFO_ENGINE_ID: "copilot" - GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}" - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.71.5" - GH_AW_INFO_WORKFLOW_NAME: "Module Cleanup — Single Module" - GH_AW_INFO_EXPERIMENTAL: "false" - GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" - GH_AW_INFO_STAGED: "false" - GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","java"]' - GH_AW_INFO_FIREWALL_ENABLED: "false" - GH_AW_INFO_AWF_VERSION: "" - GH_AW_INFO_AWMG_VERSION: "" - GH_AW_INFO_FIREWALL_TYPE: "" - GH_AW_COMPILED_STRICT: "false" - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); - await main(core, context); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - sparse-checkout: | - .github - .agents - .claude - .codex - .crush - .gemini - .opencode - .pi - sparse-checkout-cone-mode: true - fetch-depth: 1 - - name: Save agent config folders for base branch restoration - env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - - name: Check workflow lock file - id: check-lock-file - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_WORKFLOW_FILE: "module-cleanup.lock.yml" - GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); - await main(); - - name: Check compile-agentic version - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_COMPILED_VERSION: "v0.71.5" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); - await main(); - - name: Create prompt with built-in context - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl - GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} - GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - # poutine:ignore untrusted_checkout_exec - run: | - bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" - { - cat << 'GH_AW_PROMPT_919470441c431f89_EOF' - - GH_AW_PROMPT_919470441c431f89_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_919470441c431f89_EOF' - - Tools: create_issue - GH_AW_PROMPT_919470441c431f89_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_919470441c431f89_EOF' - - GH_AW_PROMPT_919470441c431f89_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_919470441c431f89_EOF' - - The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} - - **actor**: __GH_AW_GITHUB_ACTOR__ - {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} - - **repository**: __GH_AW_GITHUB_REPOSITORY__ - {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} - - **workspace**: __GH_AW_GITHUB_WORKSPACE__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ - {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} - - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ - {{/if}} - - - GH_AW_PROMPT_919470441c431f89_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_919470441c431f89_EOF' - - {{#runtime-import .github/agents/module-cleanup.agent.md}} - {{#runtime-import .github/workflows/module-cleanup.md}} - GH_AW_PROMPT_919470441c431f89_EOF - } > "$GH_AW_PROMPT" - - name: Interpolate variables and render templates - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_ENGINE_ID: "copilot" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); - await main(); - - name: Substitute placeholders - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} - GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - - const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); - - // Call the substitution function - return await substitutePlaceholders({ - file: process.env.GH_AW_PROMPT, - substitutions: { - GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, - GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, - GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, - GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST - } - }); - - name: Validate prompt placeholders - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - - name: Print prompt - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - - name: Upload activation artifact - if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: activation - include-hidden-files: true - path: | - /tmp/gh-aw/aw_info.json - /tmp/gh-aw/aw-prompts/prompt.txt - /tmp/gh-aw/github_rate_limits.jsonl - /tmp/gh-aw/base - if-no-files-found: ignore - retention-days: 1 - - agent: - needs: - - activation - - dispatch - if: needs.dispatch.outputs.has_work == 'true' - runs-on: ubuntu-latest - permissions: read-all - concurrency: - group: "gh-aw-copilot-${{ github.workflow }}" - env: - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - GH_AW_ASSETS_ALLOWED_EXTS: "" - GH_AW_ASSETS_BRANCH: "" - GH_AW_ASSETS_MAX_SIZE_KB: 0 - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - GH_AW_WORKFLOW_ID_SANITIZED: modulecleanup - outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} - checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} - effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} - has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} - model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} - output: ${{ steps.collect_output.outputs.output }} - output_types: ${{ steps.collect_output.outputs.output_types }} - setup-trace-id: ${{ steps.setup.outputs.trace-id }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Set runtime paths - id: set-runtime-paths - run: | - { - echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" - echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" - echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" - } >> "$GITHUB_OUTPUT" - - name: Create gh-aw temp directory - run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - - name: Configure gh CLI for GitHub Enterprise - run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" - env: - GH_TOKEN: ${{ github.token }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - - env: - GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULE_DIR: ${{ needs.dispatch.outputs.module_dir }} - GH_AW_NEEDS_DISPATCH_OUTPUTS_SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} - name: Export module identifiers to env - run: | - echo "MODULE_SHORT_NAME=$GH_AW_NEEDS_DISPATCH_OUTPUTS_SHORT_NAME" >> "$GITHUB_ENV" - echo "MODULE_DIR=$GH_AW_NEEDS_DISPATCH_OUTPUTS_MODULE_DIR" >> "$GITHUB_ENV" - - name: Set up JDK for running Gradle - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 - with: - distribution: temurin - java-version-file: .java-version - - name: Setup Gradle - uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e - with: - cache-read-only: true - - name: Use CLA approved bot - run: .github/scripts/use-cla-approved-bot.sh - - - name: Configure Git credentials - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - - name: Checkout PR branch - id: checkout-pr - if: | - github.event.pull_request || github.event.issue.pull_request - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); - await main(); - - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 - env: - GH_HOST: github.com - - name: Determine automatic lockdown mode for GitHub MCP Server - id: determine-automatic-lockdown - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 - env: - GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - with: - script: | - const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); - await determineAutomaticLockdown(github, context, core); - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: activation - path: /tmp/gh-aw - - name: Restore agent config folders from base branch - if: steps.checkout-pr.outcome == 'success' - env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" - run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - - name: Generate Safe Outputs Config - run: | - 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_f1f8f9950fbf9e63_EOF' - {"create_issue":{"labels":["module-cleanup"],"max":1,"title_prefix":"[module-cleanup]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_f1f8f9950fbf9e63_EOF - - name: Generate Safe Outputs Tools - env: - GH_AW_TOOLS_META_JSON: | - { - "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[module-cleanup]\". Labels [\"module-cleanup\"] will be automatically added." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_VALIDATION_JSON: | - { - "create_issue": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "parent": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "temporary_id": { - "type": "string" - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - } - } - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); - await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - - name: Start MCP Gateway - id: start-mcp-gateway - env: - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} - GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} - GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -eo pipefail - mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" - - # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="8080" - export MCP_GATEWAY_DOMAIN="localhost" - export MCP_GATEWAY_HOST_DOMAIN="localhost" - MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${MCP_GATEWAY_API_KEY}" - export MCP_GATEWAY_API_KEY - export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" - mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" - export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" - export DEBUG="*" - - export GH_AW_ENGINE="copilot" - MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') - MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/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 DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_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 GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -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 ghcr.io/github/gh-aw-mcpg:v0.3.6' - - 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_878d333d60bd5afa_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" - { - "mcpServers": { - "github": { - "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", - "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", - "GITHUB_READ_ONLY": "1", - "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" - }, - "guard-policies": { - "allow-only": { - "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", - "repos": "$GITHUB_MCP_GUARD_REPOS" - } - } - }, - "safeoutputs": { - "type": "http", - "url": "http://localhost:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" - }, - "guard-policies": { - "write-sink": { - "accept": [ - "*" - ] - } - } - } - }, - "gateway": { - "port": $MCP_GATEWAY_PORT, - "domain": "${MCP_GATEWAY_DOMAIN}", - "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" - } - } - GH_AW_MCP_CONFIG_878d333d60bd5afa_EOF - - name: Mount MCP servers as CLIs - id: mount-mcp-clis - continue-on-error: true - env: - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} - MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); - await main(); - - name: Clean credentials - continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - - name: Audit pre-agent workspace - id: pre_agent_audit - continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" - - name: Execute GitHub Copilot CLI - id: agentic_execution - # Copilot CLI tool arguments (sorted): - timeout-minutes: 30 - run: | - set -o pipefail - touch /tmp/gh-aw/agent-step-summary.md - (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - mkdir -p /tmp/ - mkdir -p /tmp/gh-aw/ - mkdir -p /tmp/gh-aw/agent/ - mkdir -p /tmp/gh-aw/sandbox/agent/logs/ - GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt 2>&1 | tee /tmp/gh-aw/agent-stdio.log - env: - COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json - GH_AW_PHASE: agent - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.71.5 - GITHUB_API_URL: ${{ github.api_url }} - GITHUB_AW: true - GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows - GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md - GITHUB_WORKSPACE: ${{ github.workspace }} - XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors - if: always() - continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" - - name: Configure Git credentials - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - - name: Copy Copilot session state files to logs - if: always() - continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - - name: Stop MCP Gateway - if: always() - continue-on-error: true - env: - MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} - run: | - bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - - name: Redact secrets in logs - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); - await main(); - env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Append agent step summary - if: always() - run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - - name: Copy Safe Outputs - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - run: | - mkdir -p /tmp/gh-aw - cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true - - name: Ingest agent output - id: collect_output - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.gradle-enterprise.cloud,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.azul.com,central.sonatype.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,develocity.apache.org,dl.google.com,dlcdn.apache.org,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com" - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_API_URL: ${{ github.api_url }} - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); - await main(); - - name: Parse agent logs for step summary - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); - await main(); - - name: Parse MCP Gateway logs for step summary - if: always() - id: parse-mcp-gateway - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); - await main(); - - name: Write agent output placeholder if missing - if: always() - run: | - if [ ! -f /tmp/gh-aw/agent_output.json ]; then - echo '{"items":[]}' > /tmp/gh-aw/agent_output.json - fi - - name: Upload agent artifacts - if: always() - continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: agent - path: | - /tmp/gh-aw/aw-prompts/prompt.txt - /tmp/gh-aw/sandbox/agent/logs/ - /tmp/gh-aw/redacted-urls.log - /tmp/gh-aw/mcp-logs/ - /tmp/gh-aw/agent-stdio.log - /tmp/gh-aw/pre-agent-audit.txt - /tmp/gh-aw/agent/ - /tmp/gh-aw/github_rate_limits.jsonl - /tmp/gh-aw/safeoutputs.jsonl - /tmp/gh-aw/agent_output.json - if-no-files-found: ignore - - conclusion: - needs: - - activation - - agent - - dispatch - - finalize - - safe_outputs - if: > - always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') - runs-on: ubuntu-slim - permissions: - contents: read - issues: write - concurrency: - group: "gh-aw-conclusion-module-cleanup" - cancel-in-progress: false - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Download agent output artifact - id: download-agent-output - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Setup agent output environment variable - id: setup-agent-output-env - if: steps.download-agent-output.outcome == 'success' - run: | - mkdir -p /tmp/gh-aw/ - find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Handle agent failure - id: handle_agent_failure - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Module Cleanup — Single Module" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_WORKFLOW_ID: "module-cleanup" - GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} - GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} - GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} - GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} - GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} - GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} - GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" - GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} - GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} - GH_AW_GROUP_REPORTS: "false" - GH_AW_FAILURE_REPORT_AS_ISSUE: "true" - GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" - GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" - GH_AW_TIMEOUT_MINUTES: "30" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); - await main(); - - dispatch: - needs: activation - if: github.repository == 'trask/opentelemetry-java-instrumentation' - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - - outputs: - has_work: ${{ steps.pick.outputs.has_work }} - module_dir: ${{ steps.pick.outputs.module_dir }} - queue_remaining: ${{ steps.pick.outputs.queue_remaining }} - short_name: ${{ steps.pick.outputs.short_name }} - wip_branch: ${{ steps.wip.outputs.wip_branch }} - steps: - - name: Configure GH_HOST for enterprise compatibility - id: ghes-host-config - shell: bash - run: | - # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct - # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. - GH_HOST="${GITHUB_SERVER_URL#https://}" - GH_HOST="${GH_HOST#http://}" - echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 1 - persist-credentials: false - - name: Resolve wip branch for this chain - id: wip - run: | - set -euo pipefail - inherited='${{ inputs.wip_branch }}' - if [ -n "$inherited" ]; then - echo "wip_branch=$inherited" >> "$GITHUB_OUTPUT" - else - echo "wip_branch=module-cleanup-wip-${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT" - fi - - name: Pick next module - id: pick - run: | - set -euo pipefail - # processed.txt lives at the root of the memory branch. - processed="" - if git fetch origin "$MEMORY_BRANCH" --depth=1 2>/dev/null; then - processed=$(git show "origin/$MEMORY_BRANCH:processed.txt" 2>/dev/null || true) - fi - # Also exclude shorts already in inflight module-cleanup PRs (their - # bodies list the modules under "## Modules in this batch" as - # `- ` + backticks + short + backticks). Once a PR merges, those - # shorts also exist in processed.txt so they won't be re-picked. - inflight=$(gh pr list --repo "$GITHUB_REPOSITORY" \ - --label "module cleanup" --state open \ - --json body --jq '.[].body' \ - | sed -n 's/^- `\([^`]*\)`$/\1/p' || true) - export REVIEW_PROGRESS="$(printf '%s\n%s\n' "$processed" "$inflight" \ - | grep -v '^$' | sort -u)" - python .github/scripts/module-cleanup/build-cleanup-matrix.py - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MEMORY_BRANCH: memory/module-cleanup - - finalize: - needs: - - agent - - dispatch - if: always() && needs.dispatch.outputs.has_work == 'true' - runs-on: ubuntu-latest - permissions: - actions: write - contents: write - pull-requests: write - - steps: - - name: Configure GH_HOST for enterprise compatibility - id: ghes-host-config - shell: bash - run: | - # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct - # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. - GH_HOST="${GITHUB_SERVER_URL#https://}" - GH_HOST="${GH_HOST#http://}" - echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 1 - persist-credentials: true - - name: Configure git author - run: .github/scripts/use-cla-approved-bot.sh - - name: Download agent artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: ./agent-artifact - continue-on-error: true - - name: Finalize - run: bash .github/scripts/module-cleanup/finalize.sh - env: - AGENT_RESULT: ${{ needs.agent.result }} - ARTIFACT_DIR: ./agent-artifact - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - QUEUE_REMAINING: ${{ needs.dispatch.outputs.queue_remaining }} - SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} - WIP_BRANCH: ${{ needs.dispatch.outputs.wip_branch }} - WORKFLOW_FILE: module-cleanup.lock.yml - - safe_outputs: - needs: - - activation - - agent - if: (!cancelled()) && needs.agent.result != 'skipped' - runs-on: ubuntu-slim - permissions: - contents: read - issues: write - timeout-minutes: 15 - env: - GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/module-cleanup" - GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}" - GH_AW_ENGINE_VERSION: "1.0.40" - GH_AW_WORKFLOW_ID: "module-cleanup" - GH_AW_WORKFLOW_NAME: "Module Cleanup — Single Module" - outputs: - code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} - code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} - create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} - create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} - created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} - process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} - process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@b8068426813005612b960b5ab0b8bd2c27142323 # v0.71.5 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Module Cleanup — Single Module" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/module-cleanup.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Download agent output artifact - id: download-agent-output - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Setup agent output environment variable - id: setup-agent-output-env - if: steps.download-agent-output.outcome == 'success' - run: | - mkdir -p /tmp/gh-aw/ - find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Configure GH_HOST for enterprise compatibility - id: ghes-host-config - shell: bash - run: | - # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct - # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. - GH_HOST="${GITHUB_SERVER_URL#https://}" - GH_HOST="${GH_HOST#http://}" - echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - - name: Process Safe Outputs - id: process_safe_outputs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "*.gradle-enterprise.cloud,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,cdn.azul.com,central.sonatype.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,develocity.apache.org,dl.google.com,dlcdn.apache.org,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com" - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"module-cleanup\"],\"max\":1,\"title_prefix\":\"[module-cleanup]\"}}" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); - await main(); - - name: Upload Safe Outputs Items - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: safe-outputs-items - path: | - /tmp/gh-aw/safe-output-items.jsonl - /tmp/gh-aw/temporary-id-map.json - if-no-files-found: ignore - From 3395506aea634aee1e1533885af21c14becb869c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 7 May 2026 09:53:09 -0700 Subject: [PATCH 11/16] up --- .github/scripts/module-cleanup/finalize.sh | 23 ++++-- .github/workflows/module-cleanup.lock.yml | 92 +++++++++++++++------- .github/workflows/module-cleanup.md | 68 ++++++++++++---- 3 files changed, 132 insertions(+), 51 deletions(-) diff --git a/.github/scripts/module-cleanup/finalize.sh b/.github/scripts/module-cleanup/finalize.sh index 1eb9f96459b4..ace490b55dc7 100644 --- a/.github/scripts/module-cleanup/finalize.sh +++ b/.github/scripts/module-cleanup/finalize.sh @@ -14,9 +14,11 @@ # the queue is empty, cut a batch branch from wip and open the PR. # Wip is NOT reset — each chain has its own wip branch, so the next # chain starts fresh on a different wip. -# 4. Self-dispatch the workflow unless we just opened a PR or the -# queue is empty (cron will pick up later). Threads WIP_BRANCH so -# the next run in the chain reuses the same wip. +# 4. Self-dispatch the workflow unless the queue is empty. After a +# PR is opened, dispatch WITHOUT inherited WIP_BRANCH so the next +# run starts a fresh chain on a fresh wip. The chain stops on its +# own once MAX_OPEN_PRS is reached (matrix script returns +# has_work=false and finalize is skipped). # # No rebase-retry loops on push: the workflow uses # concurrency.group=module-cleanup with cancel-in-progress=false, so this @@ -199,10 +201,19 @@ fi # ---- 4. Self-dispatch ---- -if [ "$OPENED_PR" = "true" ]; then - echo "Opened a PR; cron will resume the chain on its next tick." -elif [ "$QUEUE_REMAINING" -le 0 ]; then +# The chain auto-continues past PR open: after flushing, we self-dispatch +# WITHOUT inheriting WIP_BRANCH so the next run starts on a fresh +# per-chain wip (we don't want subsequent commits piling onto a wip that +# already underlies an open PR). The chain naturally terminates when +# build-cleanup-matrix.py sees MAX_OPEN_PRS reached and returns +# has_work=false, at which point neither agent nor finalize runs and no +# self-dispatch fires. Cron picks back up later. + +if [ "$QUEUE_REMAINING" -le 0 ] && [ "$OPENED_PR" != "true" ]; then echo "Queue empty; nothing to dispatch." +elif [ "$OPENED_PR" = "true" ]; then + echo "Opened a PR; self-dispatching to start a fresh chain (new wip)." + gh workflow run "$WORKFLOW_FILE" --repo "$REPO" --ref main else echo "Self-dispatching workflow for next module on $WIP_BRANCH." gh workflow run "$WORKFLOW_FILE" --repo "$REPO" --ref main \ diff --git a/.github/workflows/module-cleanup.lock.yml b/.github/workflows/module-cleanup.lock.yml index f9f6140e42b1..09782577177e 100644 --- a/.github/workflows/module-cleanup.lock.yml +++ b/.github/workflows/module-cleanup.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f6c05f53ffb41e5272ece56e2c067d880893c6b8eba67bca59045f31bad0c498","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"48aebefef96d3755319af820f11e1919e0263b1f39a2da4f58c15d86ba1f93d6","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"de0fac2e4500dabe0009e67214ff5f5447ce83dd"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"be666c2fcd27ec809703dec50e508c2fdc7f6654"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -23,18 +23,18 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # Walks instrumentation modules one-at-a-time, processing exactly one -# module per run. Each successful run's commit is appended to a -# per-chain `module-cleanup-wip-` branch. When the wip branch -# reaches FILE_THRESHOLD modified files (or when the unprocessed-module -# queue empties), the finalize job promotes wip to a `module-cleanup-batch-` -# branch and opens a PR against main. Otherwise the workflow self-dispatches -# to process the next module, threading the same wip branch. -# -# Each chain (cron tick or manual workflow_dispatch) gets its own wip -# branch named after the first run's id. If a chain dies mid-flight (e.g. -# PR creation fails), its wip is simply abandoned — the next cron tick -# starts a fresh chain on a fresh wip. Old wip and batch branches can be -# garbage-collected manually. +# module per run. Each successful run's commit is appended to a per-chain +# `module-cleanup-wip-` branch and the workflow self-dispatches +# one module at a time. When the wip branch reaches FILE_THRESHOLD +# modified files (or when the unprocessed-module queue empties), the +# finalize job promotes wip to a `module-cleanup-batch-` branch, +# opens a PR against main, and self-dispatches a fresh chain (new wip). +# The chain terminates naturally once MAX_OPEN_PRS is reached: the +# matrix script returns has_work=false and no further runs are +# Cron (every 1h) picks back up after PRs merge: when a PR merges and +# the open-PR count drops below MAX_OPEN_PRS, the next hourly tick +# starts a fresh chain. A cron tick that fires while a chain is alive +# is gated to a no-op so chains never fork in parallel. # # State: # - `memory/module-cleanup` branch holds `processed.txt` (modules already @@ -73,8 +73,8 @@ name: "Module Cleanup — Single Module" "on": schedule: - - cron: "9 */6 * * *" - # Friendly format: every 6h (scattered) + - cron: "9 */1 * * *" + # Friendly format: every 1h (scattered) workflow_dispatch: inputs: aw_context: @@ -211,23 +211,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_5c26254bd8ba649f_EOF' + cat << 'GH_AW_PROMPT_6eee91e63a294a94_EOF' - GH_AW_PROMPT_5c26254bd8ba649f_EOF + GH_AW_PROMPT_6eee91e63a294a94_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_5c26254bd8ba649f_EOF' + cat << 'GH_AW_PROMPT_6eee91e63a294a94_EOF' Tools: create_issue - GH_AW_PROMPT_5c26254bd8ba649f_EOF + GH_AW_PROMPT_6eee91e63a294a94_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_5c26254bd8ba649f_EOF' + cat << 'GH_AW_PROMPT_6eee91e63a294a94_EOF' - GH_AW_PROMPT_5c26254bd8ba649f_EOF + GH_AW_PROMPT_6eee91e63a294a94_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_5c26254bd8ba649f_EOF' + cat << 'GH_AW_PROMPT_6eee91e63a294a94_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -256,13 +256,13 @@ jobs: {{/if}} - GH_AW_PROMPT_5c26254bd8ba649f_EOF + GH_AW_PROMPT_6eee91e63a294a94_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_5c26254bd8ba649f_EOF' + cat << 'GH_AW_PROMPT_6eee91e63a294a94_EOF' {{#runtime-import .github/agents/module-cleanup.agent.md}} {{#runtime-import .github/workflows/module-cleanup.md}} - GH_AW_PROMPT_5c26254bd8ba649f_EOF + GH_AW_PROMPT_6eee91e63a294a94_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -469,9 +469,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_1a392919af0c2cbe_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_1637ad31eb04df26_EOF' {"create_issue":{"labels":["module-cleanup"],"max":1,"title_prefix":"[module-cleanup]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_1a392919af0c2cbe_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_1637ad31eb04df26_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -598,7 +598,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_95d6aefe8340e57d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f4acdfb4be68f579_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -639,7 +639,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_95d6aefe8340e57d_EOF + GH_AW_MCP_CONFIG_f4acdfb4be68f579_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -931,6 +931,38 @@ jobs: id: pick run: | set -euo pipefail + # Limit concurrency to one chain alive at a time. Cron fires + # hourly so that PRs merging mid-day promptly reopen the + # MAX_OPEN_PRS slot, but we don't want a hourly cron tick to + # fork a parallel chain on top of an already-running one. If + # any other module-cleanup run is queued or in progress, the + # cron tick exits cleanly. Self-dispatched runs (which carry + # an inherited wip_branch) skip this gate so an in-flight + # chain always makes forward progress. + # + # GitHub's run-state is the source of truth here, so there's + # no stale-lock problem: a dead/cancelled run is reported as + # completed and the next cron tick passes the gate. + if [ "$EVENT_NAME" = "schedule" ]; then + others=$(gh run list --repo "$GITHUB_REPOSITORY" \ + --workflow "$WORKFLOW_FILE" \ + --status queued --status in_progress \ + --limit 50 --json databaseId \ + | jq --arg me "$GITHUB_RUN_ID" \ + '[.[] | select((.databaseId|tostring) != $me)] | length') + echo "other queued/in_progress runs: $others" + if [ "$others" -gt 0 ]; then + echo "Chain already alive; cron exiting to avoid fork." + { + echo "has_work=false" + echo "short_name=" + echo "module_dir=" + echo "queue_remaining=0" + } >> "$GITHUB_OUTPUT" + exit 0 + fi + fi + # processed.txt lives at the root of the memory branch. processed="" if git fetch origin "$MEMORY_BRANCH" --depth=1 2>/dev/null; then @@ -948,8 +980,10 @@ jobs: | grep -v '^$' | sort -u)" python .github/scripts/module-cleanup/build-cleanup-matrix.py env: + EVENT_NAME: ${{ github.event_name }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} MEMORY_BRANCH: memory/module-cleanup + WORKFLOW_FILE: module-cleanup.lock.yml finalize: needs: diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md index d8beb667ad28..c95d5181c7c5 100644 --- a/.github/workflows/module-cleanup.md +++ b/.github/workflows/module-cleanup.md @@ -1,18 +1,18 @@ --- description: | Walks instrumentation modules one-at-a-time, processing exactly one - module per run. Each successful run's commit is appended to a - per-chain `module-cleanup-wip-` branch. When the wip branch - reaches FILE_THRESHOLD modified files (or when the unprocessed-module - queue empties), the finalize job promotes wip to a `module-cleanup-batch-` - branch and opens a PR against main. Otherwise the workflow self-dispatches - to process the next module, threading the same wip branch. - - Each chain (cron tick or manual workflow_dispatch) gets its own wip - branch named after the first run's id. If a chain dies mid-flight (e.g. - PR creation fails), its wip is simply abandoned — the next cron tick - starts a fresh chain on a fresh wip. Old wip and batch branches can be - garbage-collected manually. + module per run. Each successful run's commit is appended to a per-chain + `module-cleanup-wip-` branch and the workflow self-dispatches + one module at a time. When the wip branch reaches FILE_THRESHOLD + modified files (or when the unprocessed-module queue empties), the + finalize job promotes wip to a `module-cleanup-batch-` branch, + opens a PR against main, and self-dispatches a fresh chain (new wip). + The chain terminates naturally once MAX_OPEN_PRS is reached: the + matrix script returns has_work=false and no further runs are + Cron (every 1h) picks back up after PRs merge: when a PR merges and + the open-PR count drops below MAX_OPEN_PRS, the next hourly tick + starts a fresh chain. A cron tick that fires while a chain is alive + is gated to a no-op so chains never fork in parallel. State: - `memory/module-cleanup` branch holds `processed.txt` (modules already @@ -31,10 +31,12 @@ on: required: false type: string schedule: - # Walk-driver: each cron tick starts (or resumes) the chain. Inside a - # tick, the workflow self-dispatches one module at a time until either - # a PR is opened, the open-PR cap is hit, or the queue empties. - - cron: "every 6h" + # Cron is the primary chain driver. Each tick attempts to start a new + # chain, but the dispatch job exits if another module-cleanup run is + # already queued or in progress, so we never fork parallel chains. + # Inside a chain, the workflow self-dispatches one module at a time + # until a PR is opened, the open-PR cap is hit, or the queue empties. + - cron: "every 1h" permissions: read-all @@ -109,8 +111,42 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} MEMORY_BRANCH: memory/module-cleanup + EVENT_NAME: ${{ github.event_name }} + WORKFLOW_FILE: module-cleanup.lock.yml run: | set -euo pipefail + # Limit concurrency to one chain alive at a time. Cron fires + # hourly so that PRs merging mid-day promptly reopen the + # MAX_OPEN_PRS slot, but we don't want a hourly cron tick to + # fork a parallel chain on top of an already-running one. If + # any other module-cleanup run is queued or in progress, the + # cron tick exits cleanly. Self-dispatched runs (which carry + # an inherited wip_branch) skip this gate so an in-flight + # chain always makes forward progress. + # + # GitHub's run-state is the source of truth here, so there's + # no stale-lock problem: a dead/cancelled run is reported as + # completed and the next cron tick passes the gate. + if [ "$EVENT_NAME" = "schedule" ]; then + others=$(gh run list --repo "$GITHUB_REPOSITORY" \ + --workflow "$WORKFLOW_FILE" \ + --status queued --status in_progress \ + --limit 50 --json databaseId \ + | jq --arg me "$GITHUB_RUN_ID" \ + '[.[] | select((.databaseId|tostring) != $me)] | length') + echo "other queued/in_progress runs: $others" + if [ "$others" -gt 0 ]; then + echo "Chain already alive; cron exiting to avoid fork." + { + echo "has_work=false" + echo "short_name=" + echo "module_dir=" + echo "queue_remaining=0" + } >> "$GITHUB_OUTPUT" + exit 0 + fi + fi + # processed.txt lives at the root of the memory branch. processed="" if git fetch origin "$MEMORY_BRANCH" --depth=1 2>/dev/null; then From d6d086ab628f08c146dd74db8a4848f9248d2287 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 7 May 2026 11:24:48 -0700 Subject: [PATCH 12/16] Adopt orphan wip branches; delete wip on PR open --- .github/scripts/module-cleanup/finalize.sh | 6 ++ .github/workflows/module-cleanup.lock.yml | 65 ++++++++++++++++------ .github/workflows/module-cleanup.md | 35 +++++++++++- 3 files changed, 89 insertions(+), 17 deletions(-) diff --git a/.github/scripts/module-cleanup/finalize.sh b/.github/scripts/module-cleanup/finalize.sh index ace490b55dc7..057338064e43 100644 --- a/.github/scripts/module-cleanup/finalize.sh +++ b/.github/scripts/module-cleanup/finalize.sh @@ -196,6 +196,12 @@ if [ "$SHOULD_FLUSH" = "true" ]; then --body-file "$BODY_FILE" \ --label "module cleanup" + # The wip branch's commits now live on the batch branch and the PR. + # Delete the wip from remote so any leftover module-cleanup-wip-* + # branch is, by definition, an orphan from an interrupted chain -- + # which the next chain's dispatch step can adopt. + git push origin --delete "$WIP_BRANCH" || true + OPENED_PR=true fi diff --git a/.github/workflows/module-cleanup.lock.yml b/.github/workflows/module-cleanup.lock.yml index 09782577177e..2ea57d599049 100644 --- a/.github/workflows/module-cleanup.lock.yml +++ b/.github/workflows/module-cleanup.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"48aebefef96d3755319af820f11e1919e0263b1f39a2da4f58c15d86ba1f93d6","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c992c03fd5157fe9ab66ce6b8b490598626541c55357547e3832ef84f567a452","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"de0fac2e4500dabe0009e67214ff5f5447ce83dd"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"be666c2fcd27ec809703dec50e508c2fdc7f6654"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -211,23 +211,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_6eee91e63a294a94_EOF' + cat << 'GH_AW_PROMPT_0607cdae83fca38b_EOF' - GH_AW_PROMPT_6eee91e63a294a94_EOF + GH_AW_PROMPT_0607cdae83fca38b_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_6eee91e63a294a94_EOF' + cat << 'GH_AW_PROMPT_0607cdae83fca38b_EOF' Tools: create_issue - GH_AW_PROMPT_6eee91e63a294a94_EOF + GH_AW_PROMPT_0607cdae83fca38b_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_6eee91e63a294a94_EOF' + cat << 'GH_AW_PROMPT_0607cdae83fca38b_EOF' - GH_AW_PROMPT_6eee91e63a294a94_EOF + GH_AW_PROMPT_0607cdae83fca38b_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_6eee91e63a294a94_EOF' + cat << 'GH_AW_PROMPT_0607cdae83fca38b_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -256,13 +256,13 @@ jobs: {{/if}} - GH_AW_PROMPT_6eee91e63a294a94_EOF + GH_AW_PROMPT_0607cdae83fca38b_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_6eee91e63a294a94_EOF' + cat << 'GH_AW_PROMPT_0607cdae83fca38b_EOF' {{#runtime-import .github/agents/module-cleanup.agent.md}} {{#runtime-import .github/workflows/module-cleanup.md}} - GH_AW_PROMPT_6eee91e63a294a94_EOF + GH_AW_PROMPT_0607cdae83fca38b_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -469,9 +469,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_1637ad31eb04df26_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_44d54422cd29318a_EOF' {"create_issue":{"labels":["module-cleanup"],"max":1,"title_prefix":"[module-cleanup]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_1637ad31eb04df26_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_44d54422cd29318a_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -598,7 +598,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_f4acdfb4be68f579_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f43496d313711528_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -639,7 +639,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_f4acdfb4be68f579_EOF + GH_AW_MCP_CONFIG_f43496d313711528_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -921,11 +921,44 @@ jobs: run: | set -euo pipefail if [ -n "$INHERITED_WIP" ]; then + echo "Inheriting wip from chain: $INHERITED_WIP" echo "wip_branch=$INHERITED_WIP" >> "$GITHUB_OUTPUT" + exit 0 + fi + # No inherited wip -> this run is starting a chain. Before + # creating a fresh wip, look for an orphan wip on remote. + # An orphan exists when a prior chain was interrupted (e.g. + # cancelled by GitHub's concurrency "queue length 1" rule) + # before its wip reached the flush threshold. The chain-alive + # gate (in the pick step) ensures we never adopt a wip that + # belongs to a still-running chain. + orphan="" + while read -r _sha ref; do + [ -z "$ref" ] && continue + name="${ref#refs/heads/}" + chain_id="${name#module-cleanup-wip-}" + # Skip wips whose chain is still alive. + if [ -n "$chain_id" ] && [ "$chain_id" != "manual" ]; then + status=$(gh run view "$chain_id" --repo "$GITHUB_REPOSITORY" \ + --json status --jq .status 2>/dev/null || echo "") + if [ "$status" = "queued" ] || [ "$status" = "in_progress" ]; then + continue + fi + fi + orphan="$name" + echo "Adopting orphan wip: $orphan" + break + done < <(git ls-remote --heads origin 'module-cleanup-wip-*' || true) + + if [ -n "$orphan" ]; then + echo "wip_branch=$orphan" >> "$GITHUB_OUTPUT" else - echo "wip_branch=module-cleanup-wip-${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT" + fresh="module-cleanup-wip-${GITHUB_RUN_ID}" + echo "Starting fresh wip: $fresh" + echo "wip_branch=$fresh" >> "$GITHUB_OUTPUT" fi env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} INHERITED_WIP: ${{ inputs.wip_branch }} - name: Pick next module id: pick diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md index c95d5181c7c5..62dee820001d 100644 --- a/.github/workflows/module-cleanup.md +++ b/.github/workflows/module-cleanup.md @@ -98,13 +98,46 @@ jobs: - name: Resolve wip branch for this chain id: wip env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} INHERITED_WIP: ${{ inputs.wip_branch }} run: | set -euo pipefail if [ -n "$INHERITED_WIP" ]; then + echo "Inheriting wip from chain: $INHERITED_WIP" echo "wip_branch=$INHERITED_WIP" >> "$GITHUB_OUTPUT" + exit 0 + fi + # No inherited wip -> this run is starting a chain. Before + # creating a fresh wip, look for an orphan wip on remote. + # An orphan exists when a prior chain was interrupted (e.g. + # cancelled by GitHub's concurrency "queue length 1" rule) + # before its wip reached the flush threshold. The chain-alive + # gate (in the pick step) ensures we never adopt a wip that + # belongs to a still-running chain. + orphan="" + while read -r _sha ref; do + [ -z "$ref" ] && continue + name="${ref#refs/heads/}" + chain_id="${name#module-cleanup-wip-}" + # Skip wips whose chain is still alive. + if [ -n "$chain_id" ] && [ "$chain_id" != "manual" ]; then + status=$(gh run view "$chain_id" --repo "$GITHUB_REPOSITORY" \ + --json status --jq .status 2>/dev/null || echo "") + if [ "$status" = "queued" ] || [ "$status" = "in_progress" ]; then + continue + fi + fi + orphan="$name" + echo "Adopting orphan wip: $orphan" + break + done < <(git ls-remote --heads origin 'module-cleanup-wip-*' || true) + + if [ -n "$orphan" ]; then + echo "wip_branch=$orphan" >> "$GITHUB_OUTPUT" else - echo "wip_branch=module-cleanup-wip-${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT" + fresh="module-cleanup-wip-${GITHUB_RUN_ID}" + echo "Starting fresh wip: $fresh" + echo "wip_branch=$fresh" >> "$GITHUB_OUTPUT" fi - name: Pick next module id: pick From 302e9d23a306187797d6d95df2ad1ddffa7414d8 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 7 May 2026 11:31:11 -0700 Subject: [PATCH 13/16] Switch to fixed module-cleanup-wip branch (atomic rename on flush) --- .github/scripts/module-cleanup/finalize.sh | 62 ++++----- .github/workflows/module-cleanup.lock.yml | 144 +++++---------------- .github/workflows/module-cleanup.md | 120 +++-------------- 3 files changed, 76 insertions(+), 250 deletions(-) diff --git a/.github/scripts/module-cleanup/finalize.sh b/.github/scripts/module-cleanup/finalize.sh index 057338064e43..df7872b88e3a 100644 --- a/.github/scripts/module-cleanup/finalize.sh +++ b/.github/scripts/module-cleanup/finalize.sh @@ -8,17 +8,16 @@ # agent failed, also append to failed.txt. This guarantees a failing # module is recorded as "processed" (so it isn't retried in a loop) # AND logged as a failure for diagnostics. -# 2. If the agent produced a cleanup patch, apply it onto -# module-cleanup-wip and push. +# 2. If the agent produced a cleanup patch, apply it onto the fixed +# module-cleanup-wip branch and push. # 3. If wip diff vs origin/main has reached FLUSH_THRESHOLD files OR -# the queue is empty, cut a batch branch from wip and open the PR. -# Wip is NOT reset — each chain has its own wip branch, so the next -# chain starts fresh on a different wip. -# 4. Self-dispatch the workflow unless the queue is empty. After a -# PR is opened, dispatch WITHOUT inherited WIP_BRANCH so the next -# run starts a fresh chain on a fresh wip. The chain stops on its -# own once MAX_OPEN_PRS is reached (matrix script returns -# has_work=false and finalize is skipped). +# the queue is empty, atomically rename wip to a +# module-cleanup-batch- branch and open the PR. The wip +# branch ceases to exist on remote until the next run recreates +# it from main. +# 4. Self-dispatch the workflow unless the queue is empty. The chain +# stops on its own once MAX_OPEN_PRS is reached (matrix script +# returns has_work=false and finalize is skipped). # # No rebase-retry loops on push: the workflow uses # concurrency.group=module-cleanup with cancel-in-progress=false, so this @@ -39,13 +38,12 @@ # FLUSH_THRESHOLD - file count that triggers a PR (default 10) # WORKFLOW_FILE - workflow file name for self-dispatch # MEMORY_BRANCH - default: memory/module-cleanup -# WIP_BRANCH - per-chain wip branch (passed in by the workflow; -# defaults to module-cleanup-wip-) +# WIP_BRANCH - default: module-cleanup-wip set -euo pipefail MEMORY_BRANCH="${MEMORY_BRANCH:-memory/module-cleanup}" -WIP_BRANCH="${WIP_BRANCH:-module-cleanup-wip-${GITHUB_RUN_ID:-manual}}" +WIP_BRANCH="${WIP_BRANCH:-module-cleanup-wip}" THRESHOLD="${FLUSH_THRESHOLD:-10}" QUEUE_REMAINING="${QUEUE_REMAINING:-0}" REPO="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY required}" @@ -170,8 +168,6 @@ if [ "$SHOULD_FLUSH" = "true" ]; then RUN_ID="${GITHUB_RUN_ID:-$(date -u +%Y%m%d%H%M%S)}" BATCH_BRANCH="module-cleanup-batch-$RUN_ID" - git push origin "refs/remotes/origin/$WIP_BRANCH:refs/heads/$BATCH_BRANCH" - BODY_FILE=$(mktemp) { echo "Automated module-cleanup batch." @@ -188,6 +184,13 @@ if [ "$SHOULD_FLUSH" = "true" ]; then --reverse --format='## %s%n%n%b%n' } > "$BODY_FILE" + # Atomic rename: in one push, create the batch branch at wip's tip + # and delete the wip branch. Either both succeed or both fail, so we + # never leave wip and batch pointing at the same commits. + git push --atomic origin \ + "refs/remotes/origin/$WIP_BRANCH:refs/heads/$BATCH_BRANCH" \ + ":refs/heads/$WIP_BRANCH" + gh pr create \ --repo "$REPO" \ --base main \ @@ -196,34 +199,23 @@ if [ "$SHOULD_FLUSH" = "true" ]; then --body-file "$BODY_FILE" \ --label "module cleanup" - # The wip branch's commits now live on the batch branch and the PR. - # Delete the wip from remote so any leftover module-cleanup-wip-* - # branch is, by definition, an orphan from an interrupted chain -- - # which the next chain's dispatch step can adopt. - git push origin --delete "$WIP_BRANCH" || true - OPENED_PR=true fi # ---- 4. Self-dispatch ---- -# The chain auto-continues past PR open: after flushing, we self-dispatch -# WITHOUT inheriting WIP_BRANCH so the next run starts on a fresh -# per-chain wip (we don't want subsequent commits piling onto a wip that -# already underlies an open PR). The chain naturally terminates when -# build-cleanup-matrix.py sees MAX_OPEN_PRS reached and returns -# has_work=false, at which point neither agent nor finalize runs and no -# self-dispatch fires. Cron picks back up later. +# Always self-dispatch when there's more queued work. The next run will +# pick up wherever wip is: if we just flushed, wip is gone and the run +# starts a fresh wip from main; otherwise it appends to the same wip. +# The chain stops on its own when build-cleanup-matrix.py sees +# MAX_OPEN_PRS reached and returns has_work=false (no agent, no +# finalize, no self-dispatch). Cron picks back up later. -if [ "$QUEUE_REMAINING" -le 0 ] && [ "$OPENED_PR" != "true" ]; then +if [ "$QUEUE_REMAINING" -le 0 ]; then echo "Queue empty; nothing to dispatch." -elif [ "$OPENED_PR" = "true" ]; then - echo "Opened a PR; self-dispatching to start a fresh chain (new wip)." - gh workflow run "$WORKFLOW_FILE" --repo "$REPO" --ref main else - echo "Self-dispatching workflow for next module on $WIP_BRANCH." - gh workflow run "$WORKFLOW_FILE" --repo "$REPO" --ref main \ - --field "wip_branch=$WIP_BRANCH" + echo "Self-dispatching workflow for next module." + gh workflow run "$WORKFLOW_FILE" --repo "$REPO" --ref main fi git worktree remove --force "$MEM_WT" 2>/dev/null || true diff --git a/.github/workflows/module-cleanup.lock.yml b/.github/workflows/module-cleanup.lock.yml index 2ea57d599049..f7c127a45c74 100644 --- a/.github/workflows/module-cleanup.lock.yml +++ b/.github/workflows/module-cleanup.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c992c03fd5157fe9ab66ce6b8b490598626541c55357547e3832ef84f567a452","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d5d15125c0301304e52468b6e595abe8bd4aef5f97417e1f9c194dc9c2c4d9f0","compiler_version":"v0.71.5","agent_id":"copilot","agent_model":"${{ vars.MODULE_CLEANUP_MODEL || 'gpt-5' }}"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"de0fac2e4500dabe0009e67214ff5f5447ce83dd"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/setup-java","sha":"be666c2fcd27ec809703dec50e508c2fdc7f6654","version":"be666c2fcd27ec809703dec50e508c2fdc7f6654"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"gradle/actions/setup-gradle","sha":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e","version":"50e97c2cd7a37755bbfafc9c5b7cafaece252f6e"}],"containers":[{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -23,25 +23,29 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # Walks instrumentation modules one-at-a-time, processing exactly one -# module per run. Each successful run's commit is appended to a per-chain -# `module-cleanup-wip-` branch and the workflow self-dispatches -# one module at a time. When the wip branch reaches FILE_THRESHOLD +# module per run. Each successful run's commit is appended to the fixed +# `module-cleanup-wip` branch. When that branch reaches FILE_THRESHOLD # modified files (or when the unprocessed-module queue empties), the -# finalize job promotes wip to a `module-cleanup-batch-` branch, -# opens a PR against main, and self-dispatches a fresh chain (new wip). -# The chain terminates naturally once MAX_OPEN_PRS is reached: the -# matrix script returns has_work=false and no further runs are -# Cron (every 1h) picks back up after PRs merge: when a PR merges and -# the open-PR count drops below MAX_OPEN_PRS, the next hourly tick -# starts a fresh chain. A cron tick that fires while a chain is alive -# is gated to a no-op so chains never fork in parallel. +# finalize job atomically renames wip to `module-cleanup-batch-` +# and opens a PR against main. The next run, finding no wip on remote, +# starts a fresh wip from main. +# +# After each successful run, the workflow self-dispatches so chains keep +# moving without waiting for cron. The chain stops on its own once +# MAX_OPEN_PRS is reached (matrix returns has_work=false; finalize +# doesn't run; no self-dispatch). Cron (every 1h) restarts work after +# a PR merges and the open-PR count drops below MAX_OPEN_PRS. +# +# Because state lives on a fixed branch (not in any one run's identity), +# GitHub Actions concurrency cancellations of queued runs are harmless: +# the wip branch survives, and the next run picks up where it was. # # State: # - `memory/module-cleanup` branch holds `processed.txt` (modules already # attempted; never re-picked automatically) and `failed.txt` (a # diagnostic log of timeouts and patch-conflict failures). -# - `module-cleanup-wip-` branches hold not-yet-PR'd commits -# for an in-flight chain. +# - `module-cleanup-wip` branch holds not-yet-PR'd commits. Exists only +# while there is uncommitted work; deleted when promoted to a batch. # - Open PRs labeled `module cleanup` count toward MAX_OPEN_PRS; while at # cap, dispatch exits and waits for cron to retry. # @@ -82,10 +86,6 @@ name: "Module Cleanup — Single Module" description: Agent caller context (used internally by Agentic Workflows). required: false type: string - wip_branch: - description: Per-chain wip branch (set automatically by self-dispatch; leave blank for manual chains). - required: false - type: string permissions: {} @@ -211,23 +211,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_0607cdae83fca38b_EOF' + cat << 'GH_AW_PROMPT_10c522436a23fc31_EOF' - GH_AW_PROMPT_0607cdae83fca38b_EOF + GH_AW_PROMPT_10c522436a23fc31_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_0607cdae83fca38b_EOF' + cat << 'GH_AW_PROMPT_10c522436a23fc31_EOF' Tools: create_issue - GH_AW_PROMPT_0607cdae83fca38b_EOF + GH_AW_PROMPT_10c522436a23fc31_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_0607cdae83fca38b_EOF' + cat << 'GH_AW_PROMPT_10c522436a23fc31_EOF' - GH_AW_PROMPT_0607cdae83fca38b_EOF + GH_AW_PROMPT_10c522436a23fc31_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_0607cdae83fca38b_EOF' + cat << 'GH_AW_PROMPT_10c522436a23fc31_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -256,13 +256,13 @@ jobs: {{/if}} - GH_AW_PROMPT_0607cdae83fca38b_EOF + GH_AW_PROMPT_10c522436a23fc31_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_0607cdae83fca38b_EOF' + cat << 'GH_AW_PROMPT_10c522436a23fc31_EOF' {{#runtime-import .github/agents/module-cleanup.agent.md}} {{#runtime-import .github/workflows/module-cleanup.md}} - GH_AW_PROMPT_0607cdae83fca38b_EOF + GH_AW_PROMPT_10c522436a23fc31_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -469,9 +469,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_44d54422cd29318a_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_b706b838d5749a4f_EOF' {"create_issue":{"labels":["module-cleanup"],"max":1,"title_prefix":"[module-cleanup]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_44d54422cd29318a_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_b706b838d5749a4f_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -598,7 +598,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_f43496d313711528_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_aa50bf905b4175e6_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -639,7 +639,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_f43496d313711528_EOF + GH_AW_MCP_CONFIG_aa50bf905b4175e6_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -901,7 +901,6 @@ jobs: module_dir: ${{ steps.pick.outputs.module_dir }} queue_remaining: ${{ steps.pick.outputs.queue_remaining }} short_name: ${{ steps.pick.outputs.short_name }} - wip_branch: ${{ steps.wip.outputs.wip_branch }} steps: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config @@ -916,86 +915,10 @@ jobs: with: fetch-depth: 1 persist-credentials: false - - name: Resolve wip branch for this chain - id: wip - run: | - set -euo pipefail - if [ -n "$INHERITED_WIP" ]; then - echo "Inheriting wip from chain: $INHERITED_WIP" - echo "wip_branch=$INHERITED_WIP" >> "$GITHUB_OUTPUT" - exit 0 - fi - # No inherited wip -> this run is starting a chain. Before - # creating a fresh wip, look for an orphan wip on remote. - # An orphan exists when a prior chain was interrupted (e.g. - # cancelled by GitHub's concurrency "queue length 1" rule) - # before its wip reached the flush threshold. The chain-alive - # gate (in the pick step) ensures we never adopt a wip that - # belongs to a still-running chain. - orphan="" - while read -r _sha ref; do - [ -z "$ref" ] && continue - name="${ref#refs/heads/}" - chain_id="${name#module-cleanup-wip-}" - # Skip wips whose chain is still alive. - if [ -n "$chain_id" ] && [ "$chain_id" != "manual" ]; then - status=$(gh run view "$chain_id" --repo "$GITHUB_REPOSITORY" \ - --json status --jq .status 2>/dev/null || echo "") - if [ "$status" = "queued" ] || [ "$status" = "in_progress" ]; then - continue - fi - fi - orphan="$name" - echo "Adopting orphan wip: $orphan" - break - done < <(git ls-remote --heads origin 'module-cleanup-wip-*' || true) - - if [ -n "$orphan" ]; then - echo "wip_branch=$orphan" >> "$GITHUB_OUTPUT" - else - fresh="module-cleanup-wip-${GITHUB_RUN_ID}" - echo "Starting fresh wip: $fresh" - echo "wip_branch=$fresh" >> "$GITHUB_OUTPUT" - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - INHERITED_WIP: ${{ inputs.wip_branch }} - name: Pick next module id: pick run: | set -euo pipefail - # Limit concurrency to one chain alive at a time. Cron fires - # hourly so that PRs merging mid-day promptly reopen the - # MAX_OPEN_PRS slot, but we don't want a hourly cron tick to - # fork a parallel chain on top of an already-running one. If - # any other module-cleanup run is queued or in progress, the - # cron tick exits cleanly. Self-dispatched runs (which carry - # an inherited wip_branch) skip this gate so an in-flight - # chain always makes forward progress. - # - # GitHub's run-state is the source of truth here, so there's - # no stale-lock problem: a dead/cancelled run is reported as - # completed and the next cron tick passes the gate. - if [ "$EVENT_NAME" = "schedule" ]; then - others=$(gh run list --repo "$GITHUB_REPOSITORY" \ - --workflow "$WORKFLOW_FILE" \ - --status queued --status in_progress \ - --limit 50 --json databaseId \ - | jq --arg me "$GITHUB_RUN_ID" \ - '[.[] | select((.databaseId|tostring) != $me)] | length') - echo "other queued/in_progress runs: $others" - if [ "$others" -gt 0 ]; then - echo "Chain already alive; cron exiting to avoid fork." - { - echo "has_work=false" - echo "short_name=" - echo "module_dir=" - echo "queue_remaining=0" - } >> "$GITHUB_OUTPUT" - exit 0 - fi - fi - # processed.txt lives at the root of the memory branch. processed="" if git fetch origin "$MEMORY_BRANCH" --depth=1 2>/dev/null; then @@ -1013,10 +936,8 @@ jobs: | grep -v '^$' | sort -u)" python .github/scripts/module-cleanup/build-cleanup-matrix.py env: - EVENT_NAME: ${{ github.event_name }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} MEMORY_BRANCH: memory/module-cleanup - WORKFLOW_FILE: module-cleanup.lock.yml finalize: needs: @@ -1059,7 +980,6 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} QUEUE_REMAINING: ${{ needs.dispatch.outputs.queue_remaining }} SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} - WIP_BRANCH: ${{ needs.dispatch.outputs.wip_branch }} WORKFLOW_FILE: module-cleanup.lock.yml safe_outputs: diff --git a/.github/workflows/module-cleanup.md b/.github/workflows/module-cleanup.md index 62dee820001d..d99f2bbb15eb 100644 --- a/.github/workflows/module-cleanup.md +++ b/.github/workflows/module-cleanup.md @@ -1,41 +1,35 @@ --- description: | Walks instrumentation modules one-at-a-time, processing exactly one - module per run. Each successful run's commit is appended to a per-chain - `module-cleanup-wip-` branch and the workflow self-dispatches - one module at a time. When the wip branch reaches FILE_THRESHOLD + module per run. Each successful run's commit is appended to the fixed + `module-cleanup-wip` branch. When that branch reaches FILE_THRESHOLD modified files (or when the unprocessed-module queue empties), the - finalize job promotes wip to a `module-cleanup-batch-` branch, - opens a PR against main, and self-dispatches a fresh chain (new wip). - The chain terminates naturally once MAX_OPEN_PRS is reached: the - matrix script returns has_work=false and no further runs are - Cron (every 1h) picks back up after PRs merge: when a PR merges and - the open-PR count drops below MAX_OPEN_PRS, the next hourly tick - starts a fresh chain. A cron tick that fires while a chain is alive - is gated to a no-op so chains never fork in parallel. + finalize job atomically renames wip to `module-cleanup-batch-` + and opens a PR against main. The next run, finding no wip on remote, + starts a fresh wip from main. + + After each successful run, the workflow self-dispatches so chains keep + moving without waiting for cron. The chain stops on its own once + MAX_OPEN_PRS is reached (matrix returns has_work=false; finalize + doesn't run; no self-dispatch). Cron (every 1h) restarts work after + a PR merges and the open-PR count drops below MAX_OPEN_PRS. + + Because state lives on a fixed branch (not in any one run's identity), + GitHub Actions concurrency cancellations of queued runs are harmless: + the wip branch survives, and the next run picks up where it was. State: - `memory/module-cleanup` branch holds `processed.txt` (modules already attempted; never re-picked automatically) and `failed.txt` (a diagnostic log of timeouts and patch-conflict failures). - - `module-cleanup-wip-` branches hold not-yet-PR'd commits - for an in-flight chain. + - `module-cleanup-wip` branch holds not-yet-PR'd commits. Exists only + while there is uncommitted work; deleted when promoted to a batch. - Open PRs labeled `module cleanup` count toward MAX_OPEN_PRS; while at cap, dispatch exits and waits for cron to retry. on: workflow_dispatch: - inputs: - wip_branch: - description: "Per-chain wip branch (set automatically by self-dispatch; leave blank for manual chains)." - required: false - type: string schedule: - # Cron is the primary chain driver. Each tick attempts to start a new - # chain, but the dispatch job exits if another module-cleanup run is - # already queued or in progress, so we never fork parallel chains. - # Inside a chain, the workflow self-dispatches one module at a time - # until a PR is opened, the open-PR cap is hit, or the queue empties. - cron: "every 1h" permissions: read-all @@ -89,97 +83,18 @@ jobs: short_name: ${{ steps.pick.outputs.short_name }} module_dir: ${{ steps.pick.outputs.module_dir }} queue_remaining: ${{ steps.pick.outputs.queue_remaining }} - wip_branch: ${{ steps.wip.outputs.wip_branch }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 persist-credentials: false - - name: Resolve wip branch for this chain - id: wip - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - INHERITED_WIP: ${{ inputs.wip_branch }} - run: | - set -euo pipefail - if [ -n "$INHERITED_WIP" ]; then - echo "Inheriting wip from chain: $INHERITED_WIP" - echo "wip_branch=$INHERITED_WIP" >> "$GITHUB_OUTPUT" - exit 0 - fi - # No inherited wip -> this run is starting a chain. Before - # creating a fresh wip, look for an orphan wip on remote. - # An orphan exists when a prior chain was interrupted (e.g. - # cancelled by GitHub's concurrency "queue length 1" rule) - # before its wip reached the flush threshold. The chain-alive - # gate (in the pick step) ensures we never adopt a wip that - # belongs to a still-running chain. - orphan="" - while read -r _sha ref; do - [ -z "$ref" ] && continue - name="${ref#refs/heads/}" - chain_id="${name#module-cleanup-wip-}" - # Skip wips whose chain is still alive. - if [ -n "$chain_id" ] && [ "$chain_id" != "manual" ]; then - status=$(gh run view "$chain_id" --repo "$GITHUB_REPOSITORY" \ - --json status --jq .status 2>/dev/null || echo "") - if [ "$status" = "queued" ] || [ "$status" = "in_progress" ]; then - continue - fi - fi - orphan="$name" - echo "Adopting orphan wip: $orphan" - break - done < <(git ls-remote --heads origin 'module-cleanup-wip-*' || true) - - if [ -n "$orphan" ]; then - echo "wip_branch=$orphan" >> "$GITHUB_OUTPUT" - else - fresh="module-cleanup-wip-${GITHUB_RUN_ID}" - echo "Starting fresh wip: $fresh" - echo "wip_branch=$fresh" >> "$GITHUB_OUTPUT" - fi - name: Pick next module id: pick env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} MEMORY_BRANCH: memory/module-cleanup - EVENT_NAME: ${{ github.event_name }} - WORKFLOW_FILE: module-cleanup.lock.yml run: | set -euo pipefail - # Limit concurrency to one chain alive at a time. Cron fires - # hourly so that PRs merging mid-day promptly reopen the - # MAX_OPEN_PRS slot, but we don't want a hourly cron tick to - # fork a parallel chain on top of an already-running one. If - # any other module-cleanup run is queued or in progress, the - # cron tick exits cleanly. Self-dispatched runs (which carry - # an inherited wip_branch) skip this gate so an in-flight - # chain always makes forward progress. - # - # GitHub's run-state is the source of truth here, so there's - # no stale-lock problem: a dead/cancelled run is reported as - # completed and the next cron tick passes the gate. - if [ "$EVENT_NAME" = "schedule" ]; then - others=$(gh run list --repo "$GITHUB_REPOSITORY" \ - --workflow "$WORKFLOW_FILE" \ - --status queued --status in_progress \ - --limit 50 --json databaseId \ - | jq --arg me "$GITHUB_RUN_ID" \ - '[.[] | select((.databaseId|tostring) != $me)] | length') - echo "other queued/in_progress runs: $others" - if [ "$others" -gt 0 ]; then - echo "Chain already alive; cron exiting to avoid fork." - { - echo "has_work=false" - echo "short_name=" - echo "module_dir=" - echo "queue_remaining=0" - } >> "$GITHUB_OUTPUT" - exit 0 - fi - fi - # processed.txt lives at the root of the memory branch. processed="" if git fetch origin "$MEMORY_BRANCH" --depth=1 2>/dev/null; then @@ -226,7 +141,6 @@ jobs: SHORT_NAME: ${{ needs.dispatch.outputs.short_name }} AGENT_RESULT: ${{ needs.agent.result }} QUEUE_REMAINING: ${{ needs.dispatch.outputs.queue_remaining }} - WIP_BRANCH: ${{ needs.dispatch.outputs.wip_branch }} ARTIFACT_DIR: ./agent-artifact WORKFLOW_FILE: module-cleanup.lock.yml run: bash .github/scripts/module-cleanup/finalize.sh From d7accf821ce4fcb8db1cc058cc89d206627bdf3f Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Fri, 8 May 2026 16:52:49 +0000 Subject: [PATCH 14/16] Cleanup for executors:testing - Narrow executor testing helper visibility to package scope where sibling callers are in the same package Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../executors/AbstractExecutorServiceTest.java | 8 ++++---- .../javaagent/instrumentation/executors/TestTask.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/instrumentation/executors/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/AbstractExecutorServiceTest.java b/instrumentation/executors/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/AbstractExecutorServiceTest.java index 9286713afe0c..90a112f4fad8 100644 --- a/instrumentation/executors/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/AbstractExecutorServiceTest.java +++ b/instrumentation/executors/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/AbstractExecutorServiceTest.java @@ -32,9 +32,9 @@ protected AbstractExecutorServiceTest(T executor, InstrumentationExtension testi this.testing = testing; } - protected abstract U newTask(boolean doTraceableWork, boolean blockThread); + abstract U newTask(boolean doTraceableWork, boolean blockThread); - protected T executor() { + T executor() { return executor; } @@ -110,7 +110,7 @@ void submitCallableAndCancel() { executeAndCancelTasks(task -> executor.submit((Callable) task)); } - protected void executeTwoTasks(ThrowingConsumer task) { + void executeTwoTasks(ThrowingConsumer task) { testing.runWithSpan( "parent", () -> { @@ -137,7 +137,7 @@ protected void executeTwoTasks(ThrowingConsumer task) { .hasParent(trace.getSpan(0)))); } - protected void executeAndCancelTasks(Function> task) { + void executeAndCancelTasks(Function> task) { List children = new ArrayList<>(); List> jobFutures = new ArrayList<>(); diff --git a/instrumentation/executors/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/TestTask.java b/instrumentation/executors/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/TestTask.java index 82d51ebd85d6..fb46e60dbb6c 100644 --- a/instrumentation/executors/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/TestTask.java +++ b/instrumentation/executors/testing/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/TestTask.java @@ -7,7 +7,7 @@ import java.util.concurrent.Callable; -public interface TestTask extends Runnable, Callable { +interface TestTask extends Runnable, Callable { void unblock(); From 3b6ee2af7eb78757a662351e3d3b6c518204b9d9 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Fri, 8 May 2026 17:26:02 +0000 Subject: [PATCH 15/16] Cleanup for finagle-http-23.11:javaagent - Move shared test JVM arguments and system properties into withType().configureEach - Remove redundant isMethod() matchers from named method advice matchers - Reduce FinagleClientExtension visibility to package-private test scope Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../finagle-http-23.11/javaagent/build.gradle.kts | 15 ++------------- .../v23_11/BijectionsNettyInstrumentation.java | 6 ++---- .../finaglehttp/v23_11/FutureInstrumentation.java | 7 ++----- .../v23_11/FuturePoolInstrumentation.java | 4 +--- .../v23_11/PromiseKInstrumentation.java | 3 +-- .../v23_11/FinagleClientExtension.java | 8 ++++---- 6 files changed, 12 insertions(+), 31 deletions(-) diff --git a/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts b/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts index 156c95f0cc7e..b7295a4a7202 100644 --- a/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts +++ b/instrumentation/finagle-http-23.11/javaagent/build.gradle.kts @@ -51,11 +51,6 @@ tasks { systemProperty("collectMetadata", otelProps.collectMetadata) jvmArgs("-Dotel.instrumentation.http.client.emit-experimental-telemetry=true") jvmArgs("-Dotel.instrumentation.http.server.emit-experimental-telemetry=true") - } - - test { - jvmArgs("-Dotel.instrumentation.http.client.emit-experimental-telemetry=true") - jvmArgs("-Dotel.instrumentation.http.server.emit-experimental-telemetry=true") jvmArgs("-Dio.opentelemetry.context.enableStrictContext=true") // force the netty event loop into constrained territory @@ -64,7 +59,9 @@ tasks { systemProperty("com.twitter.finagle.netty4.numWorkers", "2") // ensure concurrent tests are competing for offload pool workers systemProperty("com.twitter.finagle.offload.numWorkers", "2") + } + test { systemProperty( "metadataConfig", "otel.instrumentation.http.client.emit-experimental-telemetry=true," + @@ -76,14 +73,6 @@ tasks { testClassesDirs = sourceSets.test.get().output.classesDirs classpath = sourceSets.test.get().runtimeClasspath jvmArgs("-Dotel.semconv-stability.opt-in=service.peer") - jvmArgs("-Dio.opentelemetry.context.enableStrictContext=true") - - // force the netty event loop into constrained territory - systemProperty("io.netty.eventLoopThreads", "2") - // ensure concurrent tests are competing for netty workers - systemProperty("com.twitter.finagle.netty4.numWorkers", "2") - // ensure concurrent tests are competing for offload pool workers - systemProperty("com.twitter.finagle.offload.numWorkers", "2") systemProperty( "metadataConfig", diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/BijectionsNettyInstrumentation.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/BijectionsNettyInstrumentation.java index 38f7126c7ae7..d5fad4a910c1 100644 --- a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/BijectionsNettyInstrumentation.java +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/BijectionsNettyInstrumentation.java @@ -5,7 +5,6 @@ package io.opentelemetry.javaagent.instrumentation.finaglehttp.v23_11; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import com.twitter.finagle.http.Request; @@ -28,10 +27,9 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer transformer) { transformer.applyAdviceToMethod( - isMethod().and(named("fullRequestToFinagle")), getClass().getName() + "$FullRequestAdvice"); + named("fullRequestToFinagle"), getClass().getName() + "$FullRequestAdvice"); transformer.applyAdviceToMethod( - isMethod().and(named("chunkedRequestToFinagle")), - getClass().getName() + "$ChunkedRequestAdvice"); + named("chunkedRequestToFinagle"), getClass().getName() + "$ChunkedRequestAdvice"); } @SuppressWarnings("unused") diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FutureInstrumentation.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FutureInstrumentation.java index 68afcf42e23a..257ae4da8bda 100644 --- a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FutureInstrumentation.java +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FutureInstrumentation.java @@ -5,7 +5,6 @@ package io.opentelemetry.javaagent.instrumentation.finaglehttp.v23_11; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import com.twitter.util.Future; @@ -30,12 +29,10 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isMethod().and(named("respond")), getClass().getName() + "$RespondAdvice"); + transformer.applyAdviceToMethod(named("respond"), getClass().getName() + "$RespondAdvice"); // transformTry is documented as not being run in the scheduler, so it's not handled - transformer.applyAdviceToMethod( - isMethod().and(named("transform")), getClass().getName() + "$TransformAdvice"); + transformer.applyAdviceToMethod(named("transform"), getClass().getName() + "$TransformAdvice"); } @SuppressWarnings("unused") diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FuturePoolInstrumentation.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FuturePoolInstrumentation.java index 9116cbcfd04d..bb5f4854efc7 100644 --- a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FuturePoolInstrumentation.java +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FuturePoolInstrumentation.java @@ -5,7 +5,6 @@ package io.opentelemetry.javaagent.instrumentation.finaglehttp.v23_11; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import io.opentelemetry.context.Context; @@ -30,8 +29,7 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isMethod().and(named("apply")), getClass().getName() + "$ApplyAdvice"); + transformer.applyAdviceToMethod(named("apply"), getClass().getName() + "$ApplyAdvice"); } @SuppressWarnings("unused") diff --git a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/PromiseKInstrumentation.java b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/PromiseKInstrumentation.java index eba5e9befe4e..216f3da1a8c3 100644 --- a/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/PromiseKInstrumentation.java +++ b/instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/PromiseKInstrumentation.java @@ -6,7 +6,6 @@ package io.opentelemetry.javaagent.instrumentation.finaglehttp.v23_11; import static net.bytebuddy.matcher.ElementMatchers.isConstructor; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -39,7 +38,7 @@ public void transform(TypeTransformer transformer) { isConstructor().and(takesArgument(0, named("com.twitter.util.Local$Context"))), getClass().getName() + "$TrapContextAdvice"); transformer.applyAdviceToMethod( - isMethod().and(named("apply").and(takesArgument(0, named("com.twitter.util.Try")))), + named("apply").and(takesArgument(0, named("com.twitter.util.Try"))), getClass().getName() + "$ApplyAdvice"); } diff --git a/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FinagleClientExtension.java b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FinagleClientExtension.java index 7c1bd76db347..23a4322d1ae3 100644 --- a/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FinagleClientExtension.java +++ b/instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/FinagleClientExtension.java @@ -32,14 +32,14 @@ * the underlying {@link EventLoopGroup} for the duration of the test class. Clients and services * are created lazily on first use and torn down once in {@code afterAll}. */ -public class FinagleClientExtension implements AfterAllCallback { +class FinagleClientExtension implements AfterAllCallback { private final UnaryOperator configurer; private final Map clients = new ConcurrentHashMap<>(); private final Map> services = new ConcurrentHashMap<>(); - public FinagleClientExtension(UnaryOperator configurer) { + FinagleClientExtension(UnaryOperator configurer) { this.configurer = configurer; } @@ -52,11 +52,11 @@ public void afterAll(ExtensionContext context) throws Exception { clients.clear(); } - public Service getService(URI uri) { + Service getService(URI uri) { return getService(uri, "https".equals(uri.getScheme()) ? ClientType.TLS : ClientType.DEFAULT); } - public Service getService(URI uri, ClientType type) { + Service getService(URI uri, ClientType type) { String dest = uri.getHost() + ":" + Utils.safePort(uri); ServiceKey key = new ServiceKey(dest, type); // Build the client and bind the service under the root OTel context so no test-trace context From 4f62f6b1e3f4d8910db2ff610673fc5ac74c1088 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Fri, 8 May 2026 17:36:59 +0000 Subject: [PATCH 16/16] Cleanup for finatra-2.9:javaagent - Qualify FinatraSingletons helper-method calls instead of static importing non-accessor helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../finatra/v2_9/FinatraRouteBuilderInstrumentation.java | 3 +-- .../finatra/v2_9/FinatraRouteInstrumentation.java | 9 +++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/instrumentation/finatra-2.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finatra/v2_9/FinatraRouteBuilderInstrumentation.java b/instrumentation/finatra-2.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finatra/v2_9/FinatraRouteBuilderInstrumentation.java index bfb4e39c6099..a9fc70828abd 100644 --- a/instrumentation/finatra-2.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finatra/v2_9/FinatraRouteBuilderInstrumentation.java +++ b/instrumentation/finatra-2.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finatra/v2_9/FinatraRouteBuilderInstrumentation.java @@ -5,7 +5,6 @@ package io.opentelemetry.javaagent.instrumentation.finatra.v2_9; -import static io.opentelemetry.javaagent.instrumentation.finatra.v2_9.FinatraSingletons.setCallbackClass; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; @@ -36,7 +35,7 @@ public static class BuildAdvice { @Advice.OnMethodExit(suppress = Throwable.class, inline = false) public static void onExit( @Advice.Return Route route, @Advice.FieldValue("callback") Function1 callback) { - setCallbackClass(route, callback.getClass()); + FinatraSingletons.setCallbackClass(route, callback.getClass()); } } } diff --git a/instrumentation/finatra-2.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finatra/v2_9/FinatraRouteInstrumentation.java b/instrumentation/finatra-2.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finatra/v2_9/FinatraRouteInstrumentation.java index 4272d9a41240..7edeef0e4204 100644 --- a/instrumentation/finatra-2.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finatra/v2_9/FinatraRouteInstrumentation.java +++ b/instrumentation/finatra-2.9/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finatra/v2_9/FinatraRouteInstrumentation.java @@ -5,10 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.finatra.v2_9; -import static io.opentelemetry.javaagent.instrumentation.finatra.v2_9.FinatraSingletons.getCallbackClass; import static io.opentelemetry.javaagent.instrumentation.finatra.v2_9.FinatraSingletons.instrumenter; -import static io.opentelemetry.javaagent.instrumentation.finatra.v2_9.FinatraSingletons.setCallbackClass; -import static io.opentelemetry.javaagent.instrumentation.finatra.v2_9.FinatraSingletons.updateServerSpanName; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -65,9 +62,9 @@ private AdviceScope(FinatraRequest request, Context context, Scope scope) { public static AdviceScope start(Route route, RouteInfo routeInfo, Class controllerClass) { Context parentContext = Context.current(); - updateServerSpanName(parentContext, routeInfo); + FinatraSingletons.updateServerSpanName(parentContext, routeInfo); - Class callbackClass = getCallbackClass(route); + Class callbackClass = FinatraSingletons.getCallbackClass(route); // We expect callback to be an inner class of the controller class. If it is not we are not // going to record it at all. FinatraRequest request; @@ -120,7 +117,7 @@ public static class CopyAdvice { @Advice.OnMethodExit(suppress = Throwable.class, inline = false) public static void onExit(@Advice.This Route route, @Advice.Return Route result) { - setCallbackClass(result, getCallbackClass(route)); + FinatraSingletons.setCallbackClass(result, FinatraSingletons.getCallbackClass(route)); } } }