Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
dedbc7b
fix(strix): retry transient visibility API failures
cursoragent Aug 17, 2026
f0dabff
test(strix): isolate GITHUB_OUTPUT in visibility CLI contract
cursoragent Aug 17, 2026
1d076f8
fix(strix): retry authenticated 403 rate-limit visibility
cursoragent Aug 17, 2026
be02bd5
fix(strix): bound rate-limit visibility backoff separately from flakes
cursoragent Aug 17, 2026
56d4677
fix(strix): extend visibility rate-limit backoff
seonghobae Aug 19, 2026
553a6bf
fix: preserve bounded visibility retry budgets
seonghobae Aug 19, 2026
61a8228
Merge branch 'main' into cursor/strix-visibility-retry-519f
opencode-agent[bot] Aug 20, 2026
3411685
Merge branch 'main' into cursor/strix-visibility-retry-519f
opencode-agent[bot] Aug 20, 2026
21beb66
test: complete Strix visibility docstring coverage
seonghobae Aug 20, 2026
c20d8c7
fix(strix): separate visibility retry budgets
seonghobae Aug 21, 2026
b44b198
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 21, 2026
eef3b8b
Merge branch 'main' into cursor/strix-visibility-retry-519f
opencode-agent[bot] Aug 21, 2026
46d5cae
docs: complete central control-plane docstrings
seonghobae Aug 21, 2026
78769dd
Merge branch 'main' into cursor/strix-visibility-retry-519f
opencode-agent[bot] Aug 21, 2026
49d133a
Merge branch 'main' into cursor/strix-visibility-retry-519f
opencode-agent[bot] Aug 21, 2026
78a935e
test(strix): track current scheduler concurrency
seonghobae Aug 21, 2026
c500598
fix(security): refresh pip audit dependency
seonghobae Aug 21, 2026
5690b45
Merge current main and refresh scheduler contracts
seonghobae Aug 21, 2026
c0fcaec
test(strix): exercise visibility gh subprocess boundary
seonghobae Aug 24, 2026
02f6e4f
Merge current main into Strix visibility retry PR
seonghobae Aug 24, 2026
4d86700
Merge main into cursor/strix-visibility-retry-519f, reconciling two
claude Aug 30, 2026
04b7a23
Merge branch 'main' into cursor/strix-visibility-retry-519f
seonghobae Aug 30, 2026
6a99b35
Merge remote-tracking branch 'origin/main' into cursor/strix-visibili…
claude Aug 30, 2026
46c6ce8
Merge main into cursor/strix-visibility-retry-519f to resolve behind …
claude Sep 2, 2026
126c214
test(strix): reproduce internal visibility and multi-header drift
seonghobae Sep 2, 2026
541acbb
test(strix): reproduce real included HTTP status classification
seonghobae Sep 2, 2026
8896e76
fix(strix): preserve internal visibility and terminal response body
seonghobae Sep 2, 2026
e78fbd0
test(strix): align CLI boundary with visibility mapping
seonghobae Sep 2, 2026
dab45d4
Merge remote-tracking branch 'origin/main' into merge-main-into-1114
claude Sep 4, 2026
6e13c8c
Merge remote-tracking branch 'origin/main' into cursor/strix-visibili…
cursoragent Sep 25, 2026
a24ba86
fix(strix): drop mixed hunks after syncing #1114 with main
cursoragent Sep 25, 2026
cc4779a
fix(strix): classify visibility failures by terminal HTTP status
cursoragent Sep 25, 2026
3612269
fix(strix): retry CONNECT-only and HTTP 408 visibility flakes
cursoragent Sep 25, 2026
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
15 changes: 2 additions & 13 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ jobs:
test -f "$trusted_strix_source/scripts/ci/strix_quick_gate.sh"
test -f "$trusted_strix_source/scripts/ci/test_strix_quick_gate.sh"
test -f "$trusted_strix_source/scripts/ci/strix_required_workflow_smoke.sh"
test -f "$trusted_strix_source/scripts/ci/strix_resolve_target_visibility.py"
{
echo "TRUSTED_STRIX_SOURCE=$trusted_strix_source"
echo "TRUSTED_STRIX_GATE=$trusted_strix_source/scripts/ci/strix_quick_gate.sh"
Expand Down Expand Up @@ -265,19 +266,7 @@ jobs:
TARGET_REPOSITORY: ${{ github.event.client_payload.target_repository || github.event.pull_request.base.repo.full_name || github.repository }}
run: |
set -euo pipefail
if [[ ! "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]]; then
echo "::error::Strix target repository must belong to ContextualWisdomLab."
exit 1
fi
is_private="$(gh api "repos/${TARGET_REPOSITORY}" --jq '.private')"
case "$is_private" in
true | false) ;;
*)
echo "::error::Target repository visibility did not resolve to true or false."
exit 1
;;
esac
echo "is_private=$is_private" >>"$GITHUB_OUTPUT"
python3 "$TRUSTED_STRIX_SOURCE/scripts/ci/strix_resolve_target_visibility.py"
Comment thread
seonghobae marked this conversation as resolved.
Outdated

- name: Materialize target workspace
if: github.event_name != 'repository_dispatch'
Expand Down
4 changes: 1 addition & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,12 @@ Semantic Versioning where the repository publishes a release.
- Run the bounded fast-mlsirm repair heartbeat at minute 49 of every hour with one-dispatch scope and a two-hour same-head floor, without weakening true-parameter recovery, CPU/GPU parity, skipped-test, or Rust-ownership gates.
- Use NVIDIA NIM `mistralai/mistral-small-4-119b-2603` with explicit high reasoning for scheduled repair and `nvidia/nemotron-3-nano-30b-a3b` for bounded helper work instead of GitHub Models in the write-capable autofix worker.
- Apply one NUL-delimited exact-path and complete pre/post-worktree verification contract to both ordinary review repair and merge-conflict repair rather than relying on a visible post-model diff for the ordinary path.

### Changed

- Avoided the expensive R/testthat failure-summary regular expression on marker-absent bounded logs by checking the required terminal marker first, while preserving fail-closed handling for incomplete or malformed failure evidence.

### Fixed

- Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched.
- Retried transient GitHub visibility lookups in the required Strix job so a timeout, 5xx, 429, empty/non-boolean response, or authenticated HTTP 403 rate-limit (installation budget / secondary rate limit) no longer aborts the scan before it starts. Rate-limit 403 uses a distinct 3-attempt budget and a 30–60s bounded wait (Retry-After / X-RateLimit-Reset when present, otherwise 30/60s). A real 401/403/404 on a missing or unauthorized repository stays fail-closed and is never treated as a source finding.
- Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109).
- Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109).
- Excluded relative `-r` and `--requirement` referrers from generated flat base-lock publication while retaining bounded include syntax diagnostics and discovering independently complete direct `.txt` children of `requirements` directories.
Expand Down
294 changes: 294 additions & 0 deletions scripts/ci/strix_resolve_target_visibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
#!/usr/bin/env python3
"""Resolve Strix target-repository visibility with fail-closed retries.

The required Strix job used a single ``gh api`` call. A transient GitHub API
flake (timeout, 5xx, 429, empty/non-boolean body, or authenticated HTTP 403
rate-limit) aborted the scan before it started. Generic flakes retry a few
times with short backoff. Authenticated HTTP 403 rate-limits use a shorter
attempt budget and a longer bounded wait, honoring Retry-After /
X-RateLimit-Reset from ``gh`` output when present and capping the sleep so
the job cannot stall. A real 401/403/404 on a missing or unauthorized
repository stays fail-closed and is never treated as success or as a source
finding.
"""

from __future__ import annotations

import argparse
import os
import re
import subprocess
import sys
import time
from collections.abc import Callable
from pathlib import Path

TARGET_REPOSITORY_RE = re.compile(r"^ContextualWisdomLab/[A-Za-z0-9_.-]+$")
HTTP_STATUS_RE = re.compile(r"\bHTTP[ /](\d{3})\b", re.IGNORECASE)
Comment thread
seonghobae marked this conversation as resolved.
Outdated
TOKEN_RE = re.compile(r"(gh[pousr]_[A-Za-z0-9]+|github_pat_[A-Za-z0-9_]+)")
RETRY_AFTER_RE = re.compile(r"(?i)\bretry-after\s*[:=]\s*(\d+)\b")
RATE_LIMIT_RESET_RE = re.compile(r"(?i)\bx-ratelimit-reset\s*[:=]\s*(\d+)\b")
PERMANENT_HTTP_STATUSES = frozenset({401, 403, 404})
TRANSIENT_HTTP_STATUSES = frozenset({429, 500, 502, 503, 504})
RATE_LIMIT_MARKERS = (
"api rate limit exceeded",
"secondary rate limit",
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
TRANSIENT_MARKERS = (
"timeout",
"timed out",
"connection reset",
"connection refused",
"connection timed out",
"context deadline exceeded",
"gateway timeout",
"i/o timeout",
"unexpected end of json",
"unexpected eof",
"temporary failure",
"server error",
"service unavailable",
)
DEFAULT_MAX_ATTEMPTS = 4
RATE_LIMIT_MAX_ATTEMPTS = 3
DEFAULT_TIMEOUT_SECONDS = 20.0
MAX_BACKOFF_SECONDS = 4.0
RATE_LIMIT_BASE_BACKOFF_SECONDS = 30.0
RATE_LIMIT_MIN_BACKOFF_SECONDS = 5.0
RATE_LIMIT_MAX_BACKOFF_SECONDS = 60.0
Comment thread
seonghobae marked this conversation as resolved.


class VisibilityResolutionError(RuntimeError):
"""Fail-closed visibility lookup that must stop the Strix job."""


class VisibilityCommandError(RuntimeError):
"""A ``gh api`` invocation failed before a boolean visibility was parsed."""


def scrub_sensitive_data(text: str) -> str:
"""Redact GitHub token prefixes before they can reach job logs."""
return TOKEN_RE.sub("<redacted>", text)


def validate_target_repository(repository: str) -> str:
"""Return a ContextualWisdomLab repository name or fail closed."""
candidate = (repository or "").strip()
if not TARGET_REPOSITORY_RE.fullmatch(candidate):
raise VisibilityResolutionError(
"Strix target repository must belong to ContextualWisdomLab."
)
return candidate


def parse_private_flag(raw: str | None) -> str | None:
"""Return ``true`` or ``false`` when visibility is an exact boolean."""
value = (raw or "").strip()
if value in {"true", "false"}:
return value
return None


def is_github_rate_limit_failure(message: str) -> bool:
"""Return whether a GitHub error is authenticated quota exhaustion."""
folded = (message or "").lower()
return any(marker in folded for marker in RATE_LIMIT_MARKERS)


def classify_gh_failure(message: str) -> str:
"""Classify a ``gh api`` failure as permanent, transient, or unknown."""
if is_github_rate_limit_failure(message):
return "transient"
status_match = HTTP_STATUS_RE.search(message or "")
if status_match is not None:
status = int(status_match.group(1))
if status in PERMANENT_HTTP_STATUSES:
return "permanent"
if status in TRANSIENT_HTTP_STATUSES:
return "transient"
return "unknown"
folded = (message or "").lower()
if any(marker in folded for marker in TRANSIENT_MARKERS):
return "transient"
return "unknown"
Comment thread
seonghobae marked this conversation as resolved.


def run_gh_visibility(
repository: str, timeout: float = DEFAULT_TIMEOUT_SECONDS
) -> str:
"""Return ``gh api`` stdout for ``repos/<repository>.private``."""
argv = ["gh", "api", f"repos/{repository}", "--jq", ".private"]
try:
completed = subprocess.run(
argv,
capture_output=True,
text=True,
shell=False,
check=False,
timeout=timeout,
)
except OSError as exc:
raise VisibilityCommandError(
scrub_sensitive_data(f"gh api could not start: {exc}")
) from exc
if completed.returncode != 0:
detail = scrub_sensitive_data(
(completed.stderr or completed.stdout or "").strip()
)
raise VisibilityCommandError(
detail or f"gh api exited {completed.returncode}"
)
return completed.stdout
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated


def backoff_seconds(attempt: int) -> float:
"""Return a short exponential backoff capped for queue health."""
return min(float(2 ** (attempt - 1)), MAX_BACKOFF_SECONDS)


def parse_rate_limit_wait_seconds(
message: str, *, now: float | None = None
) -> float | None:
"""Return a wait from Retry-After or X-RateLimit-Reset when present."""
text = message or ""
retry_after = RETRY_AFTER_RE.search(text)
if retry_after is not None:
return float(retry_after.group(1))
reset = RATE_LIMIT_RESET_RE.search(text)
if reset is None:
return None
current = time.time() if now is None else now
delay = float(reset.group(1)) - current
if delay <= 0:
return None
return delay


def rate_limit_backoff_seconds(
attempt: int, message: str = "", *, now: float | None = None
) -> float:
"""Return a bounded rate-limit wait, honoring headers when present."""
parsed = parse_rate_limit_wait_seconds(message, now=now)
if parsed is None:
parsed = min(
RATE_LIMIT_BASE_BACKOFF_SECONDS * float(2 ** (attempt - 1)),
RATE_LIMIT_MAX_BACKOFF_SECONDS,
)
parsed = max(parsed, RATE_LIMIT_MIN_BACKOFF_SECONDS)
if parsed > RATE_LIMIT_MAX_BACKOFF_SECONDS:
print(
"GitHub visibility rate-limit retry sleep capped from "
f"{parsed:g} to {RATE_LIMIT_MAX_BACKOFF_SECONDS:g} seconds.",
file=sys.stderr,
)
return RATE_LIMIT_MAX_BACKOFF_SECONDS
return parsed


def fetch_repository_visibility(
repository: str,
*,
run_gh: Callable[[str], str] | None = None,
sleep: Callable[[float], None] = time.sleep,
now: Callable[[], float] = time.time,
max_attempts: int = DEFAULT_MAX_ATTEMPTS,
) -> str:
"""Return exact ``true``/``false`` visibility after bounded retries."""
target = validate_target_repository(repository)
if max_attempts < 1:
raise VisibilityResolutionError(
"Visibility lookup requires at least one attempt."
)
runner = run_gh or run_gh_visibility
last_error = "Target repository visibility did not resolve to true or false."
rate_limit_attempts = 0
for attempt in range(1, max_attempts + 1): # pragma: no branch - last failure raises
kind = "transient"
rate_limited = False
try:
parsed = parse_private_flag(runner(target))
except subprocess.TimeoutExpired as exc:
last_error = scrub_sensitive_data(
f"GitHub visibility lookup timed out: {exc}"
)
except VisibilityCommandError as exc:
last_error = str(exc)
kind = classify_gh_failure(last_error)
rate_limited = is_github_rate_limit_failure(last_error)
if kind == "permanent":
raise VisibilityResolutionError(
"Target repository visibility lookup was denied or missing: "
f"{last_error}"
) from exc
else:
if parsed is not None:
return parsed
last_error = (
"Target repository visibility did not resolve to true or false."
)
if rate_limited:
rate_limit_attempts += 1
if rate_limit_attempts >= RATE_LIMIT_MAX_ATTEMPTS:
break
if attempt >= max_attempts or kind != "transient":
break
if rate_limited:
delay = rate_limit_backoff_seconds(attempt, last_error, now=now())
label = "rate-limit"
else:
delay = backoff_seconds(attempt)
label = "transient"
print(
f"{label.capitalize()} GitHub visibility lookup failure on attempt "
f"{attempt}/{RATE_LIMIT_MAX_ATTEMPTS if rate_limited else max_attempts}; "
f"retrying in {delay:g}s.",
file=sys.stderr,
)
sleep(delay)
Comment thread
seonghobae marked this conversation as resolved.
Outdated
Comment thread
seonghobae marked this conversation as resolved.
if is_github_rate_limit_failure(last_error):
raise VisibilityResolutionError(
"Target repository visibility lookup hit a GitHub API rate-limit; "
"this is infrastructure, not a source finding: "
f"{last_error}"
)
raise VisibilityResolutionError(last_error)


def write_is_private_output(path: Path, is_private: str) -> None:
"""Append the exact visibility boolean to ``GITHUB_OUTPUT``."""
with path.open("a", encoding="utf-8") as handle:
handle.write(f"is_private={is_private}\n")


def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"""Parse repository and GitHub-output destinations."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--repository",
default=os.environ.get("TARGET_REPOSITORY", ""),
help="ContextualWisdomLab owner/name target repository",
)
parser.add_argument(
"--github-output",
default=os.environ.get("GITHUB_OUTPUT", ""),
help="Path to the GitHub Actions GITHUB_OUTPUT file",
)
return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
"""Resolve visibility and write ``is_private`` or fail closed."""
args = parse_args(argv)
try:
if not str(args.github_output or "").strip():
raise VisibilityResolutionError("GITHUB_OUTPUT is unset.")
is_private = fetch_repository_visibility(str(args.repository))
write_is_private_output(Path(args.github_output), is_private)
except VisibilityResolutionError as exc:
print(f"::error::{exc}", file=sys.stderr)
return 1
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading