Skip to content

emrg: Windows GCM silent-fail stage 1 — no_prompt_env for all git/gh subprocess + github_status command + prompt platform guards - #545

Merged
argszero merged 1 commit into
masterfrom
feature/windows-gcm-silent-fail-stage1
Aug 7, 2026
Merged

emrg: Windows GCM silent-fail stage 1 — no_prompt_env for all git/gh subprocess + github_status command + prompt platform guards#545
argszero merged 1 commit into
masterfrom
feature/windows-gcm-silent-fail-stage1

Conversation

@argszero

Copy link
Copy Markdown
Owner

Rant (verbatim, 2026-08-07T10:17:27 UTC)

Windows 上演化反复弹 Git Credential Manager — 无凭据静默失败 + GUI 内授权
(详见 ~/.emrg/rants.jsonl;host-confirmed 5-part plan, 8 acceptance items)

Stage 1 (this PR) — daemon-side silent-fail core

1. No-credential silent failure (plan §1)

  • New git_utils.no_prompt_env(): copies the environment with GIT_TERMINAL_PROMPT=0, GCM_INTERACTIVE=never, GIT_ASKPASS= — git/gh subprocesses fail fast and silently instead of spawning GCM GUI dialogs.
  • Applied to every daemon-spawned git/gh subprocess:
    • bash_tool child processes (create_subprocess_shell)
    • all 8 scheduler git calls (rev-parse / config / tag / checkout / clone / ls-remote)
    • git_cmd() helper
  • macOS/Linux unaffected (osxkeychain helpers are non-interactive); Windows without stored credentials now errors cleanly.

2. GitHub connection status detection (plan §2)

  • New daemon command github_status{authenticated, user, method}.
  • Implementation _check_github_auth(): runs bundled gh gh auth status (via resolve_git_gh()) with 10s timeout in the prompt-free env; parses user via parse_gh_auth_user() (handles both as <user> and account <user> gh output forms). Never raises — degrades to not-authenticated.

3. Prompt platform adaptation (plan §5)

  • evolution_prompt.md + open_source_prompt.md: the git credential fill token-extraction fallback is now platform-guarded — skipped on Windows (it triggers GCM there), with a pointer to the GUI settings page; unauthenticated cycles skip GitHub ops gracefully without retrying.

Tests (+12 → 520, doc counts synced per #511 guard)

  • no_prompt_env: all three guards present + parent env preserved
  • parse_gh_auth_user: as-form / account-form / unauthenticated / empty
  • bash tool: child processes actually receive the guards (integration) + command-level override still works
  • github_status: authenticated / unauthenticated dispatch + missing-gh degradation + real-output parsing

Remaining stages (tracked in rant, kept in_progress)

  • Stage 2: GUI banner + settings-page GitHub connect block (device flow via gh auth login --web + PAT paste fallback + gh auth setup-git)
  • Stage 3: Windows clean-install acceptance (zero GCM popups in 10 min) + no-regression check

…subprocess + github_status command + prompt platform guards

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle 20260807-183131 (review angle: code correctness + subprocess coverage completeness)

Verified on ca10d53:

1. Coverage completeness — every daemon-spawned git/gh subprocess now carries the prompt-free env:

  • bash_tool create_subprocess_shell ✓
  • all 8 scheduler git subprocess.run sites (rev-parse ×2, config ×2, tag, checkout, clone, ls-remote) ✓
  • git_cmd() helper ✓
  • _check_github_auth subprocess ✓

Deliberately unguarded (correctly): _detect_git_remote uses git remote get-url (local .git/config read — no network, GCM cannot trigger); __main__.py update + client/app.py are foreground interactive CLI where the host is present — the rant scope is background-daemon popups only.

2. no_prompt_env() — copies os.environ (PATH etc. preserved, tested), sets all three guards. GIT_ASKPASS='' correctly disables askpass rather than leaving it unset.

3. _check_github_auth() — never raises (TimeoutError/OSError/ValueError caught → degraded not-authenticated); 10s timeout; stderr merged to stdout so both 'Logged in' (stdout) and error text are parsed uniformly; missing gh binary short-circuits before spawning.

4. parse_gh_auth_user() — regex handles both gh output forms ('as ' / 'account '), username charset matches GitHub rules (no leading hyphen), None on empty/unauthenticated (tested).

5. Prompt platform guards — evolution_prompt.md + open_source_prompt.md: uname-gated credential extraction (Windows skips git credential fill → GUI pointer), plus explicit no-retry guidance for unauthenticated cycles.

6. Independent test run on branch: 520 passed; imports OK; CI test run green (31170266317).

Non-blocking observation: on asyncio.wait_for timeout in _check_github_auth the gh process is not explicitly killed — harmless in practice since GCM_INTERACTIVE=never makes unauthenticated gh fail fast, but worth noting for a future hardening pass.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle 20260807-183646 (review angle: test discriminating power)

Each of the 12 new tests was checked against 'would this fail if the implementation regressed?':

Strong discriminators:

  1. test_bash_child_gets_no_prompt_env — the keystone test: runs a REAL subprocess that reads back $GIT_TERMINAL_PROMPT/$GCM_INTERACTIVE/$GIT_ASKPASS. Removing env=no_prompt_env() from bash_tool makes all three assertions fail. Verifies actual propagation, not just the helper's return value.
  2. test_bash_child_env_overridable_by_command — guards against over-engineering: if someone later 'hardens' the guard by force-resetting vars inside the shell, this fails. Correctly pins the contract (daemon sets defaults, commands may override).
  3. parse_gh_auth_user positive×2 (as/account forms) + negative×2 (unauthenticated text, empty/None) — the negative cases prevent an over-broad regex from false-positiving on error output.
  4. test_github_status_authenticated/unauthenticated — dispatch through the REAL _process_message path asserting wire frames; deleting the elif branch fails both.
  5. test_check_github_auth_no_gh_binary — pins the short-circuit degradation (no spawn, clean dict).
  6. test_check_github_auth_parses_gh_output — end-to-end fake-subprocess → authenticated dict; FakeProc's (bytes, None) correctly mirrors stderr=STDOUT.

Verification on ca10d53: 71/71 targeted tests pass locally; CI green (31170266317).

Non-blocking gaps noted (candidate follow-ups, not merge blockers):

  • _check_github_auth timeout branch (wait_for → TimeoutError → degrade) has no dedicated test
  • scheduler's 8 subprocess.run env kwargs are verified by code inspection only (no mock-based assertion per site); the bash integration test covers the same propagation mechanism

Vote: 2/3.

@argszeroargszero left a comment

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle 20260807-184016 (review angle: regression surface + Windows semantics)

Regression surface audit:

  1. bash_tool env change — the daemon has no TTY, so interactive credential prompts could never have succeeded there; they would only hang until the 30s timeout kill (exactly the rant's GCM loop). Fail-fast is strictly an improvement on all platforms. macOS osxkeychain / Linux credential helpers are non-interactive → unaffected (verified: this very evolution cycle's gh/git operations run through the same bash_tool path on macOS with no change in behavior).
  2. scheduler env change — clone/fetch of the public EMRG repo needs no credentials; private-repo evolution paths already require gh auth setup-git (Stage 2) or stored credentials. No behavior regression, only popup elimination.
  3. github_status — purely additive command; unknown msg types from older clients are ignored as before.
  4. Prompt changes — the uname gate wraps only the credential-extraction branch; Darwin/Linux execute byte-identical commands to before. Windows gets the new GUI-pointer path. No macOS/Linux regression.
  5. Head SHA immutabilityca10d53 unchanged across all 3 reviews; MERGEABLE + CLEAN vs b522165; 0 conflict markers; 520 passed locally this cycle; CI green.

Windows semantics check: GCM_INTERACTIVE=never is the documented Git Credential Manager switch for suppressing all UI; GIT_TERMINAL_PROMPT=0 covers core git; GIT_ASKPASS='' covers helper-based prompts. The three together close every HTTPS credential prompt path GCM can take.

Vote: 3/3 — merge gate satisfied (3 consecutive ✅ from different cycles, no ❌ between).

@argszero
argszero merged commit c651aa5 into masterAug 7, 2026
1 check passed
@argszero
argszero deleted the feature/windows-gcm-silent-fail-stage1 branch August 7, 2026 10:44
argszero added a commit that referenced this pull request Aug 7, 2026
…ndows TUI input & /rant visibility entries (#547)
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
argszero added a commit that referenced this pull request Aug 7, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@argszero