Skip to content

Handle no-TTY gracefully in post-checkout hook#14

Merged
albertodebortoli merged 1 commit into
mainfrom
fix/no-tty-post-checkout-graceful-skip
Jun 12, 2026
Merged

Handle no-TTY gracefully in post-checkout hook#14
albertodebortoli merged 1 commit into
mainfrom
fix/no-tty-post-checkout-graceful-skip

Conversation

@albertodebortoli

Copy link
Copy Markdown
Member

Problem

GUI Git clients (Fork, Sourcetree, Xcode, VS Code, GitKraken, etc.) run post-checkout hooks without a TTY. When switching to a branch that requires a different Luca version, the hook calls install.sh which invokes sudo to write the Luca binary to /usr/local/bin. Without a TTY, sudo cannot prompt for a password and exits with an error:

sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required
❌ ERROR: Command failed or was interrupted (exit code: 1)
[Luca] ❌ Failed to install Luca

Root Cause

install.sh:sudo_if_install_dir_not_writeable calls sudo sh -c "..." whenever /usr/local/bin is not writable by the current user. GUI clients close stdin and stdout from the terminal, so sudo has no way to prompt for a password.

Fix

The fix is surgical — only the Luca self-update path (the curl | bash install.sh invocation) requires sudo and therefore a TTY. The subsequent luca install step that syncs tools from the Lucafile does not need elevated privileges and should still run regardless of whether a TTY is present.

Before entering the self-update block, the hook now checks [ ! -t 0 ] (stdin is not a terminal):

Scenario TTY present No TTY
Luca absent Install as before Log warning, exit 0
Version mismatch Update as before Log warning with versions, fall through to luca install
Version correct Run luca install Run luca install

The version-mismatch + no-TTY case deliberately falls through to luca install so that Lucafile tools are still synchronised using the currently installed Luca binary — the wrong version is used, but that is better than silently doing nothing. The warning message makes the situation clear and directs the user to re-run setup from a terminal.

[ -t 0 ] (stdin) is used rather than [ -t 1 ] (stdout) because it is stdin that sudo needs to read the password from.

Behaviour Examples

No TTY, luca absent:

[Luca] Branch checkout detected
[Luca] Found Lucafile, synchronizing tools...
[Luca] ⚠️  Non-interactive environment detected (e.g. GUI Git client). Luca is not installed — skipping automatic installation. Please run the setup from a terminal.

Hook exits 0. No confusing sudo error. Nothing further can be done without the binary.

No TTY, version mismatch (e.g. installed 0.20.0, branch requires 0.17.0):

[Luca] Branch checkout detected
[Luca] Found Lucafile, synchronizing tools...
[Luca] ⚠️  Non-interactive environment detected (e.g. GUI Git client). Luca version mismatch (installed: 0.20.0, required: 0.17.0) — skipping automatic update. Please run the setup from a terminal to apply the correct version.
[Luca] Installing CLI tools from Lucafile...
[Luca] ✅ Tools synchronized successfully

Hook exits 0, tools are still synced.

Changes

  • post-checkout — TTY check wrapping the self-update block; no-TTY paths log actionable warnings and either exit early (no binary) or fall through to luca install (version mismatch).
  • tests/post_checkout.bats — updated one existing test that was asserting behaviour impossible in a headless (no-TTY) test environment; added two new tests covering the no-TTY paths explicitly.
  • README.md — post-checkout flowchart and sequence diagram updated to show the TTY decision point and its two outcomes.

Testing

All 14 bats tests pass. The bats runner itself has no TTY, which means the no-TTY paths are exercised naturally by the test suite without any special scaffolding.

1..14
ok 1 type guard: file checkout (type=0) exits 0 immediately
ok 2 type guard: missing third argument defaults to 0 and exits 0
ok 3 type guard: branch checkout (type=1) proceeds
ok 4 lucafile: no Lucafile in repo root exits 0 silently
ok 5 lucafile: Lucafile exists triggers synchronization message
ok 6 lucafile: git root undetermined exits 0 with warning
ok 7 luca present: curl NOT called to install when luca already in PATH
ok 8 luca absent, no-tty: skips installation with informative message and exits 0
ok 9 luca install: called with correct args on happy path
ok 10 luca install: non-zero exit warns but post-checkout exits 0
ok 11 version check: luca version matches .luca-version, curl NOT called to reinstall
ok 12 version check, no-tty: version mismatch shows warning with installed and required versions
ok 13 version check, no-tty: version mismatch still runs luca install
ok 14 version check: no .luca-version file skips version check

The TTY=true installation path (where install.sh is actually fetched and run) is not directly testable in bats since the runner has no TTY, but that code path is unchanged from the pre-fix behaviour.

GUI Git clients (Fork, Sourcetree, Xcode, VS Code, etc.) run post-checkout
hooks without a TTY, so sudo cannot prompt for a password when install.sh
needs to write to /usr/local/bin. Previously this caused the hook to fail
with a cryptic sudo error.

The fix is surgical: only the Luca self-update path (curl | bash install.sh)
requires sudo and therefore needs a TTY. The luca install step that syncs
tools from the Lucafile does not require elevated privileges and should still
run regardless.

Behaviour with no TTY:
- Luca absent: log a clear warning and exit 0 (nothing more can be done
  without the binary)
- Version mismatch: log a warning naming the installed and required versions,
  then fall through to `luca install` so Lucafile tools are still synced
  using whatever version is currently installed

Behaviour with a TTY: unchanged.

The check uses `[ ! -t 0 ]` (stdin is not a terminal) rather than stdout,
since sudo reads the password from stdin.

Tests updated to reflect that bats runs without a TTY (so the no-TTY path
is the natural path exercised). The old "luca absent: curl called to install"
test was asserting behaviour that cannot run in a headless test environment;
it is replaced by assertions on the no-TTY skip output. A new test confirms
that a version mismatch with no TTY still proceeds to `luca install`.

README diagrams for the post-checkout hook (flowchart and sequence diagram)
updated to show the TTY decision point and its two outcomes.
@albertodebortoli albertodebortoli marked this pull request as ready for review June 12, 2026 16:16
@albertodebortoli albertodebortoli merged commit 99aef78 into main Jun 12, 2026
2 checks passed
@albertodebortoli albertodebortoli deleted the fix/no-tty-post-checkout-graceful-skip branch June 12, 2026 16:21
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.

1 participant