@@ -277,6 +277,23 @@ persist_path_line() {
277277 ensure_line_in_file " ${HOME} /.bash_profile" " $1 "
278278}
279279
280+ # True when <line> (a PATH export persisted by persist_path_line) is already
281+ # present in both login files it writes — zsh ~/.zprofile and bash
282+ # ~/.bash_profile. This is the read counterpart of persist_path_line and the
283+ # same file-as-source-of-truth test the env-var checks use (check_github_token
284+ # et al. grep the dotfile rather than the live environment): once the line is
285+ # persisted, a fresh login shell puts the directory on PATH, so a check can
286+ # treat a tool installed at its known location as satisfied even when the
287+ # current `curl … | sh` process — which cannot see edits to the parent shell's
288+ # startup files — does not yet have it on PATH. Without this, such a check would
289+ # report "not on PATH" and re-run its fix on every invocation.
290+ path_line_persisted () {
291+ for _rc in " ${HOME} /.zprofile" " ${HOME} /.bash_profile" ; do
292+ grep -qF " $1 " " $_rc " 2> /dev/null || return 1
293+ done
294+ return 0
295+ }
296+
280297# Install a package by Homebrew formula (macOS) or apt package (Debian/Ubuntu).
281298# Usage: install_pkg <brew-formula> <apt-package>
282299# Sets CHECK_DETAIL and returns non-zero when it cannot install.
@@ -434,23 +451,40 @@ fix_docker() {
434451# binary can therefore exist there before that directory is on PATH.
435452DEVCONTAINER_BIN_DIR=" ${HOME} /.devcontainers/bin"
436453
437- # devcontainer CLI — installed and runnable from PATH. We treat "on PATH and
438- # executes" as the bar; a binary that exists in the default install dir but is
439- # not yet on PATH is reported separately so the fix knows to repair PATH only.
454+ # The PATH line the fix persists for the devcontainer CLI. A single constant so
455+ # the check (path_line_persisted) and the fix (persist_path_line) test and write
456+ # the exact same string — the same single-source-of-truth pattern as
457+ # GITHUB_TOKEN_LINE. Literal $HOME/$PATH so the startup shell expands them later,
458+ # not this script now (hence the SC2016 disable).
459+ # shellcheck disable=SC2016
460+ DEVCONTAINER_PATH_LINE=' export PATH="$HOME/.devcontainers/bin:$PATH"'
461+
462+ # devcontainer CLI — installed and runnable. Satisfied when it is on the live
463+ # PATH, or when it is installed at its default location and that directory's
464+ # PATH line is already persisted to the startup files (a fresh login shell will
465+ # then have it on PATH — see path_line_persisted). The latter is why a fixed
466+ # install is not re-fixed on every run: this `curl … | sh` process cannot see
467+ # PATH edits made to the parent shell's startup files, so have_cmd alone would
468+ # keep reporting "not on PATH". A binary present but with no persisted PATH line
469+ # is reported separately so the fix knows to repair PATH only.
440470check_devcontainer () {
471+ _new_term=" "
441472 if have_cmd devcontainer; then
442- _ver=" $( devcontainer --version 2> /dev/null) " || _ver=" "
443- CHECK_DETAIL=" ${_ver:- installed} "
444- return 0
445- fi
446-
447- if [ -x " ${DEVCONTAINER_BIN_DIR} /devcontainer" ]; then
473+ _dc=" devcontainer"
474+ elif [ -x " ${DEVCONTAINER_BIN_DIR} /devcontainer" ] && path_line_persisted " $DEVCONTAINER_PATH_LINE " ; then
475+ _dc=" ${DEVCONTAINER_BIN_DIR} /devcontainer"
476+ _new_term=" (open a new terminal to use it)"
477+ elif [ -x " ${DEVCONTAINER_BIN_DIR} /devcontainer" ]; then
448478 CHECK_DETAIL=" installed but ${DEVCONTAINER_BIN_DIR} is not on PATH"
449479 return 1
480+ else
481+ CHECK_DETAIL=" not installed"
482+ return 1
450483 fi
451484
452- CHECK_DETAIL=" not installed"
453- return 1
485+ _ver=" $( " $_dc " --version 2> /dev/null) " || _ver=" "
486+ CHECK_DETAIL=" ${_ver:- installed}${_new_term} "
487+ return 0
454488}
455489
456490# Install the devcontainer CLI via the official script when missing, then put
@@ -464,10 +498,7 @@ fix_devcontainer() {
464498 fi
465499 fi
466500
467- # Literal $HOME/$PATH so the startup shell expands them later, not now.
468- # shellcheck disable=SC2016
469- _line=' export PATH="$HOME/.devcontainers/bin:$PATH"'
470- persist_path_line " $_line "
501+ persist_path_line " $DEVCONTAINER_PATH_LINE "
471502
472503 case " :${PATH} :" in
473504 * " :${DEVCONTAINER_BIN_DIR} :" * ) ;;
@@ -730,38 +761,54 @@ fix_checksum_secret() {
730761# devcontainer CLI above).
731762CLAUDE_BIN_DIR=" ${HOME} /.local/bin"
732763
733- # Claude Code (the `claude` CLI) — installed, on PATH, signed in, and current.
734- # Login is probed non-interactively with `claude auth status --json`, which
735- # reports `"loggedIn": true` for a usable session; the interactive
764+ # The PATH line the fix persists for Claude Code — the single source of truth
765+ # shared by the check (path_line_persisted) and the fix (persist_path_line), as
766+ # with DEVCONTAINER_PATH_LINE above. Literal $HOME/$PATH for the startup shell
767+ # (SC2016).
768+ # shellcheck disable=SC2016
769+ CLAUDE_PATH_LINE=' export PATH="$HOME/.local/bin:$PATH"'
770+
771+ # Claude Code (the `claude` CLI) — installed, reachable, signed in, and current.
772+ # It is "reachable" when on the live PATH, or installed at its default location
773+ # with its PATH line already persisted (a fresh login shell will then find it —
774+ # see path_line_persisted); that second case is what stops a fixed install from
775+ # being re-fixed every run, since this `curl … | sh` process can't see PATH
776+ # edits to the parent shell's startup files. The binary is resolved once (by its
777+ # absolute path when only persisted, else by name) and the same checks run on
778+ # it: login is probed non-interactively with `claude auth status --json`, which
779+ # reports `"loggedIn": true` for a usable session — the interactive
736780# `claude auth login` lives in the fix, never here, so a check never blocks
737- # waiting for a browser. When installed and signed in, the check also runs
738- # `claude update` to keep the install current on every run — best-effort, so a
739- # failed or already-current update never flips a healthy check to failing
740- # ("up to date" is reported only when the update actually succeeds). A binary
741- # present in the default install dir but not yet on PATH is reported separately
742- # so the fix knows to repair PATH only.
781+ # waiting for a browser. When signed in, the check also runs `claude update` to
782+ # keep the install current — best-effort, so a failed or already-current update
783+ # never flips a healthy check to failing ("up to date" is reported only when the
784+ # update actually succeeds). A binary present with no persisted PATH line is
785+ # reported separately so the fix knows to repair PATH only.
743786check_claude () {
787+ _new_term=" "
744788 if have_cmd claude; then
745- if claude auth status --json 2> /dev/null | grep -qE ' "loggedIn"[[:space:]]*:[[:space:]]*true' ; then
746- if claude update > /dev/null 2>&1 ; then
747- _upd=" , up to date"
748- else
749- _upd=" "
750- fi
751- _ver=" $( claude --version 2> /dev/null | cut -d' ' -f1) " || _ver=" "
752- CHECK_DETAIL=" ${_ver: +v${_ver} , } logged in${_upd} "
753- return 0
754- fi
755- CHECK_DETAIL=" installed but not logged in — run 'claude auth login'"
756- return 1
757- fi
758-
759- if [ -x " ${CLAUDE_BIN_DIR} /claude" ]; then
789+ _claude=" claude"
790+ elif [ -x " ${CLAUDE_BIN_DIR} /claude" ] && path_line_persisted " $CLAUDE_PATH_LINE " ; then
791+ _claude=" ${CLAUDE_BIN_DIR} /claude"
792+ _new_term=" (open a new terminal to use it)"
793+ elif [ -x " ${CLAUDE_BIN_DIR} /claude" ]; then
760794 CHECK_DETAIL=" installed but ${CLAUDE_BIN_DIR} is not on PATH"
761795 return 1
796+ else
797+ CHECK_DETAIL=" not installed"
798+ return 1
762799 fi
763800
764- CHECK_DETAIL=" not installed"
801+ if " $_claude " auth status --json 2> /dev/null | grep -qE ' "loggedIn"[[:space:]]*:[[:space:]]*true' ; then
802+ if " $_claude " update > /dev/null 2>&1 ; then
803+ _upd=" , up to date"
804+ else
805+ _upd=" "
806+ fi
807+ _ver=" $( " $_claude " --version 2> /dev/null | cut -d' ' -f1) " || _ver=" "
808+ CHECK_DETAIL=" ${_ver: +v${_ver} , } logged in${_upd}${_new_term} "
809+ return 0
810+ fi
811+ CHECK_DETAIL=" installed but not logged in — run 'claude auth login'"
765812 return 1
766813}
767814
@@ -778,10 +825,7 @@ fix_claude() {
778825 fi
779826 fi
780827
781- # Literal $HOME/$PATH so the startup shell expands them later, not now.
782- # shellcheck disable=SC2016
783- _line=' export PATH="$HOME/.local/bin:$PATH"'
784- persist_path_line " $_line "
828+ persist_path_line " $CLAUDE_PATH_LINE "
785829
786830 case " :${PATH} :" in
787831 * " :${CLAUDE_BIN_DIR} :" * ) ;;
0 commit comments