Skip to content

install-hooks: prompt for author via the controlling terminal - #2231

Open
svarlamov-git-ai wants to merge 4 commits into
feat/timed-line-readerfrom
feat/author-prompt-dev-tty
Open

install-hooks: prompt for author via the controlling terminal#2231
svarlamov-git-ai wants to merge 4 commits into
feat/timed-line-readerfrom
feat/author-prompt-dev-tty

Conversation

@svarlamov-git-ai

@svarlamov-git-ai svarlamov-git-ai commented Aug 27, 2026

Copy link
Copy Markdown

Summary

The recommended install paths never showed the author prompt from #2227: curl install.sh | bash gives install-hooks the curl pipe as stdin, and install.ps1 runs & $exe install-hooks --env | Out-Host, piping stdout. Following git's credential-prompt pattern, the prompt now falls back to the controlling terminal/dev/tty on Unix, CONIN$/CONOUT$ on Windows (mechanism in #2230) — when stdio is redirected but a terminal exists.

No installer changes. All unattended-environment detection is in-binary, and it is compatible with older binaries driving the auto-update flow (an old binary fetches and runs the new install script with only its own env vars set):

  • author_prompt_suppressed() reuses the binary's existing is_superuser_expected_environment() detector — CI markers, containers, K8s, MDM expectations, and GIT_AI_DAEMON_UPGRADE (set by every released binary's daemon-silent upgrade path; verified against v1.6.24 and v1.7.0).
  • GIT_AI_BACKGROUND_UPGRADE_WORKER=1 — set by every released binary when spawning the background upgrade worker, which runs in the user's session with an openable /dev/tty and would otherwise pop a prompt mid-session.
  • is_in_background_agent() — AI-agent sandboxes (Claude web, Devin, codex-cloud, GIT_AI_CLOUD_AGENT).
  • Truthy GIT_AI_NO_AUTHOR_PROMPT remains an explicit opt-out for anyone who wants one, but nothing depends on it.
  • A backgrounded install (git-ai install-hooks &, nohup) is skipped via a foreground-process-group check (SIGTTIN would stop the process, freezing even the prompt's timeout).

Channel resolution (resolve_prompt_io): GIT_AI_TEST_FORCE_TTY → piped stdio (test semantics); stdin+stdout TTYs → stdio; else controlling terminal; else skip. The terminal is only opened after the cheap config gates pass, and the reader thread only spawns after the full gate passes.

Tests (TDD, red first)

  • Suppression tests for CI environments, daemon upgrades, the background upgrade worker, and agent sandboxes (an ambient-env scrub is applied to every prompt test so each exercises its own gate even when the suite itself runs in CI).
  • Linux pty end-to-end: runs install-hooks under script(1) with stdin redirected to /dev/null — non-tty stdin, but the pty is the controlling terminal; input piped through script reaches the /dev/tty prompt and the confirmed author lands in config.json. Self-skips where util-linux script is unavailable.
  • Test harness: detach_from_controlling_terminal helper (setsid / DETACHED_PROCESS — deliberately not CREATE_NO_WINDOW, which still allocates a hidden console); all test children default to GIT_AI_NO_AUTHOR_PROMPT=1 so no test can reach a developer's real terminal, with prompt tests opting back in via =0.

Notes / risks

  • Manual (non-silent) git-ai upgrade on Windows performed by an older binary runs the installer on a hidden console with no suppression env; the prompt falls back to the invisible CONIN$ and times out after 15s, once, then the install continues. New binaries set GIT_AI_NO_AUTHOR_PROMPT=1 on that spawn, so this self-heals after one upgrade cycle. The auto (daemon/background) flows are fully suppressed for old binaries via their pre-existing env vars.
  • Windows console codepage: CONIN$ bytes may be non-UTF-8 for non-ASCII input → read error → prompt skips, install unaffected (no ReadConsoleW; would need new windows-sys features).
  • No automated Windows positive-path test (a conhost can't be fabricated portably in cargo test); the skip side is covered via DETACHED_PROCESS and the mechanism is symmetric with the Linux pty e2e. Manual install.ps1 verification in a real console recommended before release.
  • sudo bash install.sh from a terminal prompts as root and writes /root/.git-ai — identical to the already-shipped TTY-under-sudo behavior; JAMF/MDM runs have no controlling terminal and skip cleanly.

🤖 Generated with Claude Code

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

svarlamov and others added 2 commits August 27, 2026 03:29
The recommended installs pipe stdio — `curl install.sh | bash` makes
stdin the script pipe, and install.ps1 pipes stdout through Out-Host —
so the author prompt's stdin/stdout TTY gate never passed there. Follow
git's credential-prompt pattern: when stdio is redirected but the
process has a controlling terminal, prompt over /dev/tty (Unix) or
CONIN$/CONOUT$ (Windows) instead.

Because a controlling terminal can be openable in non-interactive
flows, the prompt now hard-skips for daemon-triggered silent upgrades
(GIT_AI_DAEMON_UPGRADE), the background upgrade worker
(GIT_AI_BACKGROUND_UPGRADE_WORKER, which runs in the user's session and
does not set the daemon-upgrade env), and background AI-agent sandboxes.

Tests: suppression coverage for all three guards; a Linux end-to-end
test that drives the prompt through a real pty via script(1) with stdin
redirected to /dev/null; and a detach helper (setsid / DETACHED_PROCESS)
so skip-assertions hold even when the test runner itself has a terminal.
Test children default to GIT_AI_NO_AUTHOR_PROMPT=1 so no unrelated test
can ever reach a developer's terminal; prompt tests opt back in with =0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The controlling-terminal fallback made the prompt reachable from flows
where a terminal is openable but nobody can answer:

- Windows `git-ai upgrade` runs the installer on a hidden
  CREATE_NO_WINDOW console where CONIN$ opens but is invisible.
- `install-hooks --wsl` nests installs in each distro on the same
  hidden console's pty.
- The macOS pkg postinstall keeps the controlling tty when run from a
  terminal (`sudo installer -pkg`).
- install.sh in CI/MDM/containers can have a pty (pty-backed CI steps,
  `ssh -tt`); reuse its existing CI/MDM detection to suppress.
- Backgrounded installs (`git-ai install-hooks &`, nohup) would be
  stopped by SIGTTIN on the first terminal read, freezing even the
  timeout; skip when not in the terminal's foreground process group.

All now set/honor GIT_AI_NO_AUTHOR_PROMPT (name extracted to a
pub(crate) const). Also: gates reordered so the terminal is only opened
after the cheap config gates pass (removing the unreachable match arm
and the now-tautological should_prompt_for_author helper); save-path
warnings route through the prompt channel where the user can see them,
including a new warning when the pre-save reload fails; skip/banner
wording no longer implies partial saves and is ASCII-only for legacy
console codepages.

Tests: in-process install_hooks::run tests now isolate HOME and the
prompt env (they bypass child-command isolation); positive prompt tests
scrub inherited background-agent markers; the pty test passes the
binary path via the environment (quote-safe) and self-skips on
non-util-linux script(1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@svarlamov-git-ai
svarlamov-git-ai force-pushed the feat/author-prompt-dev-tty branch from 897df49 to 9e51833 Compare August 27, 2026 03:29
@svarlamov
svarlamov force-pushed the feat/author-prompt-dev-tty branch from 9e51833 to ab13aeb Compare August 27, 2026 15:14
Constraint: the stack must not change any installer (install.sh,
install.ps1, pkg/MSI packaging, nix) and must stay compatible with
older binaries driving the auto-update flow, which fetch and run the
new install script with only their own env vars set.

Revert the install.sh and macOS postinstall suppression hunks and
instead have author_prompt_suppressed() consult the binary's existing
is_superuser_expected_environment() detector (CI, containers, K8s, MDM
expectations, and GIT_AI_DAEMON_UPGRADE). Combined with the
GIT_AI_BACKGROUND_UPGRADE_WORKER check, every suppression signal is one
that released binaries (verified against v1.6.24 and v1.7.0) already
set on their daemon-silent and background upgrade paths, so no
installer or spawner cooperation is required.

Tests: new CI-environment suppression test; the ambient scrub helper
now also clears is_superuser_expected_environment() markers and is
applied to every prompt test, so each test exercises its own gate even
when the suite itself runs in CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@svarlamov-git-ai
svarlamov-git-ai force-pushed the feat/author-prompt-dev-tty branch from ab13aeb to b568aad Compare August 27, 2026 15:56
- Suppress the prompt for root-run installs on Unix (curl | sudo bash,
  ssh -tt root@ provisioning): HOME resolves to root's, so a typed
  identity would land in a config.json the real user's git-ai never
  reads. Deliberately ignores GIT_AI_ALLOW_SUPERUSER, which install.sh
  exports for every root run.
- Add a --no-author-prompt flag for spawners that cannot set env vars
  (e.g. packaged-installer command lines). Binary-side only; wiring any
  packaging to it is left to a separate PR since this stack must not
  touch installers.
- Enter-confirming empty defaults now prints the same "skipping author
  setup" guidance as the timeout/EOF paths instead of silently saving
  nothing.
- Replace the prompt gate's hand-rolled author/api-key resolution with
  Config::fresh() (canonical env>file precedence), keeping an explicit
  corrupt-config-file guard so an unparseable config.json still skips
  before prompting.
- Export UNATTENDED_ENV_MARKERS (now also driving
  is_superuser_expected_environment) and BACKGROUND_AGENT_ENV_MARKERS so
  the test scrubber stays in lockstep with production detection instead
  of hand-copying marker lists; use the named env constants across test
  files. Prompt-firing tests self-skip where an unscrubbable marker
  (/.dockerenv, root) forces suppression.
- De-flake timed_line_reader_times_out (60s reader block vs 30s bound).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to 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.

2 participants