diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 58f11efc90..3909ea3b9c 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -643,7 +643,7 @@ jobs: # PR-head source, credentials, lifecycle execution, or runner # command files. coverage_tool_image="opencode-coverage-tools:${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - coverage_build_dir="${RUNNER_TEMP}/opencode-coverage-tool-build" + coverage_build_dir="${RUNNER_TEMP}/opencode-coverage-tool-build-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" trusted_ci_requirements="${GITHUB_WORKSPACE}/requirements-opencode-review-ci-hashes.txt" trusted_noema_document_requirements="${GITHUB_WORKSPACE}/requirements-noema-document-ci-hashes.txt" trusted_base_python_installer="${GITHUB_WORKSPACE}/scripts/ci/install_base_python_locks.py" @@ -2389,6 +2389,21 @@ jobs: exit 1 fi + - name: Clean up coverage runner resources + if: always() + env: + COVERAGE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-coverage-${{ github.run_id }}-${{ github.run_attempt }} + COVERAGE_BUILD_DIR: ${{ runner.temp }}/opencode-coverage-tool-build-${{ github.run_id }}-${{ github.run_attempt }} + run: | + set -euo pipefail + # The sandbox writes as uid 65532; the runner cannot remove its files. + sudo rm -rf -- "$COVERAGE_SOURCE_WORKDIR" + sudo rm -rf -- "$COVERAGE_BUILD_DIR" + coverage_tool_image="opencode-coverage-tools:${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + if docker image inspect "$coverage_tool_image" >/dev/null 2>&1; then + docker image rm "$coverage_tool_image" + fi + opencode-review-target: name: opencode-review needs: [validate-pr-metadata, coverage-evidence] diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 57ff68b271..8a80d65461 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -2572,7 +2572,10 @@ def test_opencode_privileged_review_security_boundaries_are_fail_closed(): measure_step = coverage_job.index( " - name: Measure test and docstring evidence\n" ) - measure = coverage_job[measure_step:] + cleanup_step = coverage_job.index( + " - name: Clean up coverage runner resources\n", measure_step + ) + measure = coverage_job[measure_step:cleanup_step] target_start = coverage_end + 1 target_job = workflow[target_start:] @@ -2592,7 +2595,7 @@ def test_opencode_privileged_review_security_boundaries_are_fail_closed(): assert "actions: read" in coverage_job assert "contents: read" not in coverage_job assert 'GITHUB_TOKEN: ""' in coverage_job - assert syntax_step < measure_step + assert syntax_step < measure_step < cleanup_step assert "\n - name:" not in measure.split("\n run: |", 1)[1] assert 'UV_NO_BUILD: "1"' in measure assert measure.count("GITHUB_ENV=/dev/null") == 3 diff --git a/tests/test_opencode_workflow_shell_syntax.py b/tests/test_opencode_workflow_shell_syntax.py index b0a672b1a2..3e30633eb7 100644 --- a/tests/test_opencode_workflow_shell_syntax.py +++ b/tests/test_opencode_workflow_shell_syntax.py @@ -43,6 +43,7 @@ def test_opencode_review_run_blocks_are_valid_bash(): for step_name in ( "Materialize pull request merge tree for coverage measurement", + "Clean up coverage runner resources", "Prepare bounded OpenCode review evidence", "Enforce changed-file syntax gate", "Publish bounded OpenCode review comment", @@ -61,6 +62,59 @@ def test_opencode_review_run_blocks_are_valid_bash(): assert result.returncode == 0, f"{step_name}: {result.stderr}" +def test_coverage_cleanup_removes_only_the_current_attempt(tmp_path: Path): + workflow = (REPO_ROOT / ".github/workflows/opencode-review-dispatch.yml").read_text() + coverage_job = workflow.split("\n coverage-evidence:\n", 1)[1].split( + "\n opencode-review-target:\n", 1 + )[0] + marker = " - name: Clean up coverage runner resources\n" + step = coverage_job.split(marker, 1)[1] + assert " if: always()\n" in step.split(" run: |", 1)[0] + + owned = tmp_path / "opencode-coverage-42-2" + other = tmp_path / "opencode-coverage-41-1" + build = tmp_path / "opencode-coverage-tool-build-42-2" + other_build = tmp_path / "opencode-coverage-tool-build-41-1" + owned.mkdir() + other.mkdir() + build.mkdir() + other_build.mkdir() + fake_bin = tmp_path / "bin" + fake_bin.mkdir() + sudo = fake_bin / "sudo" + sudo.write_text('#!/bin/sh\nexec "$@"\n') + docker = fake_bin / "docker" + docker.write_text('#!/bin/sh\nprintf "%s\\n" "$*" >> "$DOCKER_LOG"\n') + sudo.chmod(0o755) + docker.chmod(0o755) + log = tmp_path / "docker.log" + result = subprocess.run( + ["bash", "-e"], + input=_extract_run_block(workflow, "Clean up coverage runner resources"), + text=True, + capture_output=True, + check=False, + env={ + **os.environ, + "PATH": f"{fake_bin}:{os.environ['PATH']}", + "DOCKER_LOG": str(log), + "COVERAGE_SOURCE_WORKDIR": str(owned), + "COVERAGE_BUILD_DIR": str(build), + "GITHUB_RUN_ID": "42", + "GITHUB_RUN_ATTEMPT": "2", + }, + ) + assert result.returncode == 0, result.stderr + assert not owned.exists() + assert not build.exists() + assert other.is_dir() + assert other_build.is_dir() + assert log.read_text().splitlines() == [ + "image inspect opencode-coverage-tools:42-2", + "image rm opencode-coverage-tools:42-2", + ] + + def test_opencode_review_comment_helpers_are_shared_and_valid_bash(): workflow_text = (REPO_ROOT / ".github/workflows/opencode-review-dispatch.yml").read_text( encoding="utf-8" diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 1161602a53..3c0594c6aa 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -17,7 +17,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "58f11efc9072e35db6ec6365630db9f97d16f652" +REVIEW_DISPATCH_BLOB_SHA = "3909ea3b9c285ba3dac09e0cccb6618fdb9adad6" def _workflow_text(path: Path) -> str: