fix(agent-team): dispatch via GitHub App so P3 reaches CI (run_id resolves) #63

Merged
amoussa1229 merged 8 commits from fix/agent-team-dispatch-run-id into main 2026-06-24 22:22:19 +00:00
3 changed files with 14 additions and 9 deletions
Showing only changes of commit 78104e8934 - Show all commits

View file

@ -477,8 +477,6 @@ def _app_dispatch_seams() -> "tuple[Any, Any, Any] | None":
return None # partial -> inert, NEVER raise, NEVER log key
try:
from pathlib import Path
pem = Path(key_path).read_text(encoding="utf-8")
except Exception: # noqa: BLE001 - unreadable key -> inert, never crash serve
_LOG.warning(

View file

@ -747,7 +747,6 @@ def app_run_locator(
*,
_http: Any = None,
_sleep: Any = None,
_now: Any = None,
) -> RunLocator:
"""App-token :class:`RunLocator`: match the triggered run via the REST API.
@ -767,16 +766,15 @@ def app_run_locator(
treating the error body as "no runs" and silently exhausting the poll window.
``_http`` injects a ``requests``-like client (``.get(url, *, params=...,
headers=..., timeout=...)`` -> response exposing ``.json()``); ``_sleep`` /
``_now`` are injected for tests. No token ever appears in a log or message.
headers=..., timeout=...)`` -> response exposing ``.json()``); ``_sleep`` is
injected for tests. No token ever appears in a log or message.
"""
sleep = _sleep if _sleep is not None else None
def _locate(*, owner: str, repo: str, task_id: str, since_iso: str) -> str | None:
import time
from datetime import timedelta
do_sleep = sleep if sleep is not None else time.sleep
do_sleep = _sleep if _sleep is not None else time.sleep
try:
floor_dt = datetime.strptime(since_iso, "%Y-%m-%dT%H:%M:%SZ").replace(
@ -818,7 +816,9 @@ def app_run_locator(
# instead of treating a 401/403/404 error body as "no runs" and
# silently exhausting the ~60s poll window. A genuine 200 with no
# matching run still falls through to the None-on-no-match path below.
status = getattr(resp, "status_code", 200)
# Default a missing status_code to None (fail closed -> raise), never
# to 200 (which would treat a malformed response as success).
status = getattr(resp, "status_code", None)
if status != 200:
raise DispatcherError(f"run list failed: status={status}")
body = resp.json()

View file

@ -188,10 +188,17 @@ def _parse_expires_at(expires_at: str) -> datetime:
fails closed rather than propagating a bare ``ValueError``.
"""
try:
return datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
parsed = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
except (ValueError, AttributeError) as exc:
# Avoid even the literal substring "tok" so a naive secret scan / a test
# asserting the token value is absent cannot false-positive on the word.
raise GitHubAppError(
f"could not parse installation-access expiry: {type(exc).__name__}"
) from None
# Normalise to aware UTC: a value lacking an offset would parse to a naive
# datetime, and comparing it against the aware ``now`` in TokenProvider.token
# would raise a bare TypeError (bypassing the fail-closed GitHubAppError
# contract). GitHub always sends ``Z``, so this is defensive.
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc)
return parsed