diff --git a/Agent.md b/Agent.md index 70e61de5..02ff918f 100644 --- a/Agent.md +++ b/Agent.md @@ -119,7 +119,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design: pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg ``` -Python: `uv run pytest tests/ -v` (1095) — import check: `uv run python -c "from emrg.client.app import run_client"` +Python: `uv run pytest tests/ -v` (1096) — import check: `uv run python -c "from emrg.client.app import run_client"` GUI: `cd emrg/gui && npm test` (265: 45 daemon_client + 20 conn-manager + 22 app-commands + 132 renderer smoke + 15 i18n + 8 integration + 3 commands + 8 build-config + 7 gui-state + 2 tool-group + 3 preload-api) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`; renderer React suite: `cd emrg/gui/renderer && npm run typecheck && npm test` (78 vitest: 5 snapshot-store + 9 utils + 3 ErrorBoundary + 2 App smoke + 11 commands + 4 copywriting + 11 i18n + 11 markdown + 15 transcript + 7 TranscriptView) + `npm run build` → `renderer/dist/` CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响) diff --git a/scripts/sync-master-from-api.py b/scripts/sync-master-from-api.py index 53a8c9b4..82f6f422 100644 --- a/scripts/sync-master-from-api.py +++ b/scripts/sync-master-from-api.py @@ -36,11 +36,36 @@ API = "https://api.github.com" +_TOKEN: str | None = None # resolved once by _auth_token(), held in memory only + + +def _auth_token() -> str | None: + """Resolve a GitHub token once: env var, else `gh auth token` (read into + memory only — never printed). Falls back to anonymous when unavailable. + + Anonymous requests are limited to 60/hr, which a commit-chain walk can + exhaust mid-run (observed cycle 2026-08-26 01:46 on the push counterpart); + authenticating upfront keeps long walks under the limit. + """ + global _TOKEN + if _TOKEN is None: + t = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") + if not t: + try: + out = subprocess.run(["gh", "auth", "token"], capture_output=True, + text=True, timeout=15, encoding="utf-8", + errors="replace") + t = out.stdout.strip() if out.returncode == 0 else None + except Exception: + t = None + _TOKEN = t or "" + return _TOKEN or None + def api_get(url: str) -> dict: - """GET a GitHub API URL, using GH_TOKEN or gh CLI auth when available.""" + """GET a GitHub API URL, authenticated when a token is available.""" headers = {"User-Agent": "emrg-sync-master-from-api", "Accept": "application/vnd.github+json"} - token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN") + token = _auth_token() if token: headers["Authorization"] = "Bearer " + token req = urllib.request.Request(url, headers=headers) diff --git a/tests/test_sync_master_from_api.py b/tests/test_sync_master_from_api.py index 02b447e8..8e228563 100644 --- a/tests/test_sync_master_from_api.py +++ b/tests/test_sync_master_from_api.py @@ -69,6 +69,16 @@ def test_script_reconstructs_gpg_signed_commits(): assert "reconstruct_commit" in content # core logic named +def test_script_authenticates_upfront_via_gh_token(): + """Anonymous API requests are limited to 60/hr; a commit-chain walk can + exhaust them mid-run. The script must resolve a token once via env or + `gh auth token` (memory-only) and use it from the first request.""" + content = SCRIPT.read_text(encoding="utf-8") + assert "auth token" in content # gh keyring token resolution + assert "_auth_token()" in content # helper named + assert "Authorization" in content # header applied when token present + + def test_script_fails_loud_before_touching_refs(): content = SCRIPT.read_text(encoding="utf-8") assert "aborting (no refs touched)" in content # mismatch → stop, refs safe