diff --git a/commands/host/claude-update b/commands/host/claude-update index 2429749..b060cd3 100644 --- a/commands/host/claude-update +++ b/commands/host/claude-update @@ -16,8 +16,8 @@ RELEASE_CHANNEL_URL="https://downloads.claude.ai/claude-code-releases/latest" # Parse flags: # -y / DDEV_CLAUDE_CODE_AUTOUPDATE update now, no questions asked # -n only report whether an update exists -# (nothing) prompt; or, with no terminal (e.g. a -# post-start hook), behave like -n +# (nothing) prompt; or, with no interactive terminal, +# behave like -n mode="prompt" while [ $# -gt 0 ]; do case "$1" in @@ -28,7 +28,9 @@ while [ $# -gt 0 ]; do esac shift done -if [ "$mode" = "prompt" ] && [ -n "${DDEV_CLAUDE_CODE_AUTOUPDATE:-}" ]; then +# The env var beats -n too, so the post-start hook -- which passes -n to stay +# non-interactive -- still auto-updates for anyone who opted in. +if [ "$mode" != "yes" ] && [ -n "${DDEV_CLAUDE_CODE_AUTOUPDATE:-}" ]; then mode="yes" fi @@ -113,8 +115,10 @@ case "$mode" in report_only ;; prompt) - # No terminal (post-start hook, CI, piped): report and exit, like -n. - if [ ! -t 0 ]; then + # No interactive terminal (CI, piped, or a DDEV hook whose output we do not own): + # report and exit, like -n. Both ends must be a tty -- a hook can hand us a tty on + # stdin while capturing stdout, and prompting there hangs the start that spawned us. + if [ ! -t 0 ] || [ ! -t 1 ]; then report_only exit 0 fi diff --git a/config.claude-code.yaml b/config.claude-code.yaml index 8d606b1..2f2daf2 100644 --- a/config.claude-code.yaml +++ b/config.claude-code.yaml @@ -6,4 +6,6 @@ hooks: - exec: "rm -rf ~/.claude || true; mkdir -p /var/www/html/.ddev/claude-code/.claude && ln -s /var/www/html/.ddev/claude-code/.claude ~/.claude" # Report (or, with DDEV_CLAUDE_CODE_AUTOUPDATE set, apply) any Claude Code update. # Runs on the host so it can read that env var and offer `ddev restart --no-cache`. - - exec-host: "ddev claude-update" + # -n keeps it non-interactive: prompting here would block the `ddev start` that + # spawned the hook, on a terminal DDEV does not own. + - exec-host: "ddev claude-update -n" diff --git a/tests/test.bats b/tests/test.bats index d916c97..42a3a92 100644 --- a/tests/test.bats +++ b/tests/test.bats @@ -186,6 +186,17 @@ teardown() { assert_success run ddev exec claude --version assert_output --partial "${latest_version}" + + # The env var also beats an explicit -n. The post-start hook passes -n so it can + # never prompt, so opting into auto-updates has to keep working through that flag. + run ddev exec 'PATH="$HOME/.local/bin:$PATH" claude install '"${old_version}"' --force' + assert_success + export DDEV_CLAUDE_CODE_AUTOUPDATE=1 + run ddev claude-update -n + unset DDEV_CLAUDE_CODE_AUTOUPDATE + assert_success + run ddev exec claude --version + assert_output --partial "${latest_version}" } # The post-start hook runs `ddev claude-update` on every start. By default it only