emrg: GitHub PAT auth in GUI settings + gh auth setup-git (GCM rant Stage 2) - #548
Conversation
…tage 2) Windows GCM rant (2026-08-07T10:17:27) acceptance items 4/5 (PAT fallback + setup-git) + settings-page auth section groundwork. Daemon (emrg/server/daemon.py): - github_connect command: gh auth login --with-token (token via stdin, never argv) + gh auth setup-git so git uses gh as credential helper and push/pull/fetch never falls back to GCM popups; re-verifies via the github_status parser; never raises. - github_disconnect command: gh auth logout --hostname github.com. - Both dispatch to github_connect_result / github_disconnect_result frames. GUI: - Settings dialog gains a 'GitHub connection' section: live status from github_status, PAT input + Connect / Disconnect buttons, zh/en i18n. - daemon_client RESPONSE_TYPES: github_connect / github_disconnect maps. - main.js IPC handlers (emrg:githubStatus/Connect/Disconnect) + preload bridge exposure. Tests: +8 Python (dispatch, empty-token, gh-missing, PAT stdin delivery, login failure, setup-git failure, disconnect); GUI RESPONSE_TYPES test covers the two new mappings. Docs synced 534 -> 542 (doc-count guard).
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle (review angle: coverage completeness + token safety)
Verified at head 08e1b0e (CI 31176205802 green):
- Token never in argv:
proc.communicate(token + b'\n')delivers the PAT via stdin; the testtest_github_connect_pat_login_sets_up_gitasserts BOTH argv shape (["auth", "login", "--with-token"]) and stdin delivery (received == b"ghp_x\n") — a regression moving the token into argv fails the test. Discriminating. - Prompt-free env: every subprocess (login, status re-verify, setup-git, logout) runs under
no_prompt_env()→ no GCM/askpass popup even on Windows. - Single source of truth for re-verify:
_github_connectre-uses_check_github_auth(the same parser asgithub_status), so the user shown in the GUI is the one gh actually reports. - setup-git is the key acceptance item (git ops must not fall back to GCM) and is executed after auth; failure surfaces as
error="auth ok but gh auth setup-git failed"with ok=True (degraded but informative). - Negative states all covered: empty token rejected before subprocess, gh-missing degrade, login failure (401 reported), setup-git failure, disconnect paths — 8 new Python tests, 542 total green.
- GUI end-to-end wiring: daemon dispatch → RESPONSE_TYPES → main IPC (10s status / 40s auth timeouts) → preload bridge → settings section; RESPONSE_TYPES test extended for both new mappings (91 GUI tests green). i18n parity: 14 zh keys == 14 en keys, zero mismatches.
Non-blocking observation: when setup-git fails (ok=True with error note), the GUI success toast doesn't surface the error field — acceptable degraded state, could be a follow-up.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle (review angle: Windows semantics + GUI data flow)
Second review at head 08e1b0e (CI 31176205802 green), different angle from the first:
- Windows GCM-avoidance chain is complete:
gh auth login --with-tokenwrites gh's own credential store (not git's), sono_prompt_env()(GIT_TERMINAL_PROMPT=0/GCM_INTERACTIVE=never/GIT_ASKPASS=) does NOT interfere with gh's auth — the env vars only guard git subprocesses. Thengh auth setup-gitmakes git delegate to gh (credential helper), so subsequentgit push/pull/fetchread the token from gh's store without ever touching GCM. This is exactly the acceptance item's requirement. - Token transport is fully pipe-only: renderer → preload → main → ws frame → daemon stdin. It never appears in argv anywhere in the chain;
sendCommandAndWaitframes are in-memory; the daemoncommunicate(token + '\n')feeds stdin. Log redaction (#513/#515) coversghp_-style tokens if any frame ever hit a log. - Timeout hierarchy is correct: PENDING_TIMEOUT_MS (5s default) would be too short for login+setup-git, but main.js explicitly passes 40s for connect/disconnect and 10s for status — the ws-level timeout doesn't clobber the subprocess-level 30s timeouts.
- GUI feedback loop is coherent: connect → githubConnectResult → status refresh re-queries github_status (single source of truth), disconnect toggles the row visibility via refresh. No divergent state paths.
- Both auth paths converge on the same status surface: github_connect (PAT) and github_connect_web (#549 device flow) both end with the settings section showing "Connected as @user" via the same refreshGithubStatus — consistent UX.
Full suite on branch: 542 py + 91 gui green. i18n parity 14 zh == 14 en.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle (review angle: regression surface + mergeability)
Third review at head 08e1b0e (CI 31176205802 green):
- Scope is exactly the feature: 13 files, +431/−3 — daemon (2 commands + dispatch), GUI wiring (daemon_client/main/preload/index/dialogs/i18n), tests, and the doc-count sync (534→542). The only non-feature files are the three doc files updated purely for the test-count guard — verified each diff is a one-line count change, no prose drift.
- Regression surface is minimal: the new github_connect/github_disconnect branches in _process_message sit alongside the existing github_status handler — no changes to any pre-existing branch. The new methods are additive to EmrgServer (no signature changes to existing methods). The GUI additions are all new IPC handlers + new DOM section — no pre-existing UI path was altered.
- Prompt-free guarantee holds under the new paths: every new subprocess (login/setup-git/logout) uses no_prompt_env() — the acceptance item's core promise (no GCM popup) is preserved across ALL daemon git/gh invocation sites, not just the pre-existing ones.
- Mergeability: MERGEABLE + clean, CI green on the exact head, no conflicts with master d426639 (nothing merged since branch creation), 542 py + 91 gui verified locally.
- Failure modes are non-fatal: every new command degrades to an error frame or ok=False rather than raising — the daemon never crashes on a bad token, missing gh, or gh timeout (30s subprocess + 40s IPC timeouts).
Merge gate: 3 consecutive ✅ from distinct cycles (195936 coverage/token-safety, 200821 Windows/GUI-flow, this cycle regression/mergeability), no ❌ in between → merging.
… 2c) Last code item of the Windows GCM rant (2026-08-07T10:17:27): a lazy banner prompting the host to connect GitHub when evolution actually needs it and auth is missing. Trigger (per the rant's 'only when evolution really needs GitHub'): the moment evolution_count grows — that is exactly when the evolution cycle produced something that must be pushed to GitHub. Local chat never triggers it (no banner at startup). - index.html: new #github-banner element (msg + Connect + Dismiss). - layout.css: pill banner styled like conn-banner. - app.js: maybeShowGithubBanner() queries github_status; shows the banner when unauthenticated; Connect opens the settings dialog (lands on the GitHub section from #548); Dismiss hides it for the session. - i18n: zh/en (3 keys). - renderer smoke test: positive (unauthenticated -> banner appears) + negative (authenticated -> stays hidden) + source assertions; mock seeded with 'hidden' first so the assertions discriminate (initial-state trap). - GUI tests 91 -> 92.
… 2c) Last code item of the Windows GCM rant (2026-08-07T10:17:27): a lazy banner prompting the host to connect GitHub when evolution actually needs it and auth is missing. Trigger (per the rant's 'only when evolution really needs GitHub'): the moment evolution_count grows — that is exactly when the evolution cycle produced something that must be pushed to GitHub. Local chat never triggers it (no banner at startup). - index.html: new #github-banner element (msg + Connect + Dismiss). - layout.css: pill banner styled like conn-banner. - app.js: maybeShowGithubBanner() queries github_status; shows the banner when unauthenticated; Connect opens the settings dialog (lands on the GitHub section from #548); Dismiss hides it for the session. - i18n: zh/en (3 keys). - renderer smoke test: positive (unauthenticated -> banner appears) + negative (authenticated -> stays hidden) + source assertions; mock seeded with 'hidden' first so the assertions discriminate (initial-state trap). - GUI tests 91 -> 92.
… 2c) (#550) * emrg: GitHub connect banner when evolution needs auth (GCM rant Stage 2c) Last code item of the Windows GCM rant (2026-08-07T10:17:27): a lazy banner prompting the host to connect GitHub when evolution actually needs it and auth is missing. Trigger (per the rant's 'only when evolution really needs GitHub'): the moment evolution_count grows — that is exactly when the evolution cycle produced something that must be pushed to GitHub. Local chat never triggers it (no banner at startup). - index.html: new #github-banner element (msg + Connect + Dismiss). - layout.css: pill banner styled like conn-banner. - app.js: maybeShowGithubBanner() queries github_status; shows the banner when unauthenticated; Connect opens the settings dialog (lands on the GitHub section from #548); Dismiss hides it for the session. - i18n: zh/en (3 keys). - renderer smoke test: positive (unauthenticated -> banner appears) + negative (authenticated -> stays hidden) + source assertions; mock seeded with 'hidden' first so the assertions discriminate (initial-state trap). - GUI tests 91 -> 92. * emrg: fix banner test placement — top-level, not nested in model test CI 31177342281 caught a real test-harness issue: the GCM banner test was injected INSIDE the '多模型管理' parent test body (before its closing brace), making it a subtest that the parent cancelled before it finished ('test did not finish before its parent and was cancelled'). Locally the timing hid it; CI's node --test runner surfaced it. Relocate the banner test after the parent's closing '});' so it runs as a top-level test. 92 GUI tests pass locally + under EMRG_SKIP_INTEGRATION=1. --------- Co-authored-by: EMRG Evolution <emrg@argszero.dev>
…p tolerance (#552) Version bump 0.2.10 → 0.2.11 across all 6 version sources (pyproject.toml / emrg/__init__.py / gui/package.json / uv.lock / make-installer.sh / build-runtime.sh). Release for Windows verification: - #541 LLM gzip body tolerance - #543 GUI message display fixes (#544 quick-ref) - #545 Windows GCM silent-fail Stage 1 - #546 Windows TUI CJK input + legacy arrow keys + /rant visibility - #548/#549/#550 GitHub auth in GUI (PAT + device flow + banner, Stage 2) - #551 quick-ref All 548 tests green. Co-authored-by: EMRG Evolution <emrg@argszero.dev>
Summary
Stage 2 of the Windows GCM popup rant (2026-08-07T10:17:27): GUI-side GitHub
authentication so evolution can push PRs without ever triggering GCM popups.
Daemon (emrg/server/daemon.py)
github_connectcommand: runsgh auth login --with-token(tokendelivered via stdin, never argv — argv would leak the token into process
listings), then
gh auth setup-gitso git uses gh as its credentialhelper and
git push/pull/fetchnever falls back to GCM. Re-verifies thesession through the existing
github_statusparser. Never raises.github_disconnectcommand:gh auth logout --hostname github.laiyagushi.com.github_connect_result/github_disconnect_result.GUI
github_statusgh auth login --with-token+ setup-git)daemon_client.jsRESPONSE_TYPES:github_connect/github_disconnectmappings;
main.jsIPC handlers (emrg:githubStatus/Connect/Disconnect)with preload bridge exposure.
Tests
PAT delivered via stdin, login-failure reporting, setup-git-failure
reporting, disconnect paths) — 542 total.
Remaining (later cycles)
gh auth login --webcode display.