Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions .github/scripts/check_pin_freshness.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@ def _fetch_json(url: str, token: str) -> dict[str, object] | None:
return payload if isinstance(payload, dict) else None


def _action_repo(action: str) -> str:
"""Return `owner/repo` for an action string that may carry a sub-path.

Action references can be `owner/repo` or `owner/repo/path/to/subaction`
(e.g. `github/codeql-action/init`). Only the first two slash-segments
name the GitHub repository — the trailing segments are paths within
the repo's tree (containing per-subaction `action.yml` files). The
REST API endpoint we hit (`/repos/<owner>/<repo>/git/...`) only
accepts the `owner/repo` form; passing the full action string would
404 on every sub-path action and surface as a false-positive
"tag no longer resolves" finding.
"""
parts = action.split("/", 2)
return "/".join(parts[:2]) if len(parts) >= 2 else action


def _resolve_tag_sha(action: str, tag: str, token: str) -> str | None:
"""Return the commit SHA the tag points at, or None on missing/error.

Expand All @@ -117,7 +133,8 @@ def _resolve_tag_sha(action: str, tag: str, token: str) -> str | None:
commit. Lightweight tags resolve in one GET (the ref's `object.sha`
is the commit directly).
"""
ref = _fetch_json(f"{_API_BASE}/repos/{action}/git/refs/tags/{tag}", token)
repo = _action_repo(action)
ref = _fetch_json(f"{_API_BASE}/repos/{repo}/git/refs/tags/{tag}", token)
if ref is None:
return None
obj = ref.get("object")
Expand All @@ -131,7 +148,7 @@ def _resolve_tag_sha(action: str, tag: str, token: str) -> str | None:
return obj_sha
if obj_type == "tag":
# Annotated tag — dereference to the commit it points at.
annotated = _fetch_json(f"{_API_BASE}/repos/{action}/git/tags/{obj_sha}", token)
annotated = _fetch_json(f"{_API_BASE}/repos/{repo}/git/tags/{obj_sha}", token)
if annotated is None:
return None
inner = annotated.get("object")
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
Expand All @@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
Expand All @@ -44,7 +44,7 @@ jobs:
# Pure in-process tests — completes fast so PR authors get quick feedback.
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
Expand All @@ -57,7 +57,7 @@ jobs:
# Enforces [tool.coverage.report].fail_under from pyproject.toml (75%).
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
Expand All @@ -69,7 +69,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
Expand All @@ -84,7 +84,7 @@ jobs:
# secret past the first defence layer.
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
Expand Down Expand Up @@ -218,7 +218,7 @@ jobs:
# actual workflow jobs on disk.
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
Expand All @@ -234,7 +234,7 @@ jobs:
# while PR titles fail in CI (or vice versa).
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ jobs:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"
2 changes: 1 addition & 1 deletion .github/workflows/eval-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ inputs.python_version || '3.14' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
# annotation when a new release lands and you've reviewed the diff.
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.14"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "harness-python-react"
version = "0.2.9"
version = "0.2.10"
description = "Production-quality LLM-driven coding harness — Python (FastAPI) backend, Vite + React + TypeScript frontend."
readme = "README.md"
requires-python = ">=3.14"
Expand Down
43 changes: 43 additions & 0 deletions tests/test_check_pin_freshness.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,49 @@ def test_resolve_returns_none_on_malformed_payload() -> None:
assert cpf._resolve_tag_sha("foo/bar", "v1.0.0", "fake") is None


# ---------- _action_repo (sub-path normalisation) ----------


def test_action_repo_passthrough_for_owner_repo() -> None:
assert cpf._action_repo("actions/checkout") == "actions/checkout"


def test_action_repo_strips_subpath() -> None:
"""`github/codeql-action/init` → `github/codeql-action` (subpath isn't a repo)."""
assert cpf._action_repo("github/codeql-action/init") == "github/codeql-action"


def test_action_repo_strips_deep_subpath() -> None:
"""Deeply nested sub-actions still strip back to owner/repo."""
assert cpf._action_repo("owner/repo/path/to/sub-action") == "owner/repo"


def test_resolve_tag_sha_uses_owner_repo_for_subpath_action(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Regression for the false-positive 404 on sub-path actions.

Before this fix, _resolve_tag_sha passed `github/codeql-action/init` as
the API path segment, hitting `/repos/github/codeql-action/init/...`
which 404s (init is a tree path, not a repo). The audit then reported
`init@v4 — upstream tag no longer resolves` even though `v4` resolves
fine on `github/codeql-action`.
"""
seen_urls: list[str] = []

def fake_fetch(url: str, _token: str) -> dict[str, object] | None:
seen_urls.append(url)
return {"object": {"type": "commit", "sha": "deadbeef" * 5}}

monkeypatch.setattr(cpf, "_fetch_json", fake_fetch)
sha = cpf._resolve_tag_sha("github/codeql-action/init", "v4", "fake")
assert sha == "deadbeef" * 5
assert (
seen_urls[0]
== "https://api.github.com/repos/github/codeql-action/git/refs/tags/v4"
), seen_urls


# ---------- _check_tag_pin ----------


Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading