From 3c0a49480e0c05b7480edf5dbdec3cc4e8c27141 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 14:57:15 +0000 Subject: [PATCH] fix(install): sync the hook scripts with the CLI and name the user's shell in the next steps - hooks/: copy the CLI's embedded hooks (shelltime/cli model/hooks). Curl installs got older scripts until `shelltime init` rewrote them: bash and zsh recorded the previous command again on an empty Enter, and fish ran gc and track on the terminal's input while it was still probing the terminal. - install.bash: the reload step names the user's own shell config from $SHELL (`source ~/.bashrc`) and offers opening a new terminal, so `shelltime init` isn't run in a shell that doesn't have it on PATH yet. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014EpZRgMC528MvzhF6oiKHo --- CLAUDE.md | 2 +- hooks/bash.bash | 5 ++++- hooks/fish.fish | 8 +++++--- hooks/zsh.zsh | 12 +++++++++++- install.bash | 11 ++++++++++- 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3c02ff9..1e42e9e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ This is the **shelltime installation** repository - a collection of shell script - `install.bash` - Main installation script that downloads shelltime CLI binaries and sets up shell hooks - `cloud.bash` - Setup for Claude Code cloud environments, run from an environment's setup script with `curl -fsSL https://raw.githubusercontent.com/shelltime/installation/main/cloud.bash | bash`. Installs the Linux CLI and daemon into `~/.shelltime/bin`, runs `shelltime cc install`, and adds a Claude Code SessionStart hook (`~/.shelltime/bin/cloud-session-start.sh`, embedded in the script) that signs in and starts the daemon in each session. The token is a network secret of the environment (host `api.shelltime.xyz`, header `Authorization`, prefix `CLI`) that Anthropic's agent proxy adds to requests outside the VM, so the hook signs in with the placeholder `proxy-injected` (the CLI then sends no `Authorization` header; needs a CLI release with `model.ProxyInjectedToken`). `SHELLTIME_TOKEN` in the environment variables, for environments without network secrets, replaces the placeholder. Setup scripts get neither. It installs nothing unless `CLAUDE_CODE_REMOTE` is `true`, which cloud environments set (the setup script inherits it from `environment-manager`) and which is never true on a user's own machine. A setup script that exits non-zero stops sessions from starting, so it always exits 0 (its `EXIT` trap reports a failure instead), and nothing runs before the final `main` call, so a truncated download installs nothing. The guide that documents it is `content/blogs/track-claude-code-cloud-sessions/` in the web repo; keep the two in step -- `hooks/` - Shell-specific hook scripts that track command execution: +- `hooks/` - Shell-specific hook scripts that track command execution. They are copies of the CLI's embedded hooks (`model/hooks/` in shelltime/cli, which `shelltime hooks install` and `shelltime init` write over these), so change both together: - `zsh.zsh` - Uses zsh's native `preexec` and `precmd` hooks - `bash.bash` - Uses `bash-preexec` library for preexec/precmd functionality - `fish.fish` - Uses fish's `fish_preexec` and `fish_postexec` events diff --git a/hooks/bash.bash b/hooks/bash.bash index 7787714..86a6274 100644 --- a/hooks/bash.bash +++ b/hooks/bash.bash @@ -48,6 +48,9 @@ precmd_invoke_cmd() { local LAST_RESULT=$? # BASH_COMMAND in precmd is the *previous* command local CMD="$LAST_COMMAND" + # Consume it. precmd also runs for an empty prompt (the user pressed + # enter), and leaving LAST_COMMAND set would record the same command again. + LAST_COMMAND="" # Check if command starts with exit, logout, or reboot if [[ "$CMD" =~ ^(exit|logout|reboot) ]]; then return @@ -68,4 +71,4 @@ precmd_invoke_cmd() { # Set the functions for bash-preexec preexec_functions+=(preexec_invoke_cmd) -precmd_functions+=(precmd_invoke_cmd) \ No newline at end of file +precmd_functions+=(precmd_invoke_cmd) diff --git a/hooks/fish.fish b/hooks/fish.fish index da25227..6088cb3 100644 --- a/hooks/fish.fish +++ b/hooks/fish.fish @@ -4,7 +4,9 @@ if not command -v shelltime > /dev/null echo "Warning: shelltime CLI not found. Please install it to enable time tracking." else - shelltime gc + # Detached from the tty: this runs while fish is still probing the terminal + # (OSC 11 / CPR / DA1) at startup, and must not sit on the input queue. + shelltime gc < /dev/null end # Create a timestamp for the session when the shell starts @@ -19,7 +21,7 @@ function fish_preexec --on-event fish_preexec return end - shelltime track -s=fish -id=$SESSION_ID -cmd="$argv" -p=pre --ppid=$FISH_PPID > /dev/null + shelltime track -s=fish -id=$SESSION_ID -cmd="$argv" -p=pre --ppid=$FISH_PPID < /dev/null > /dev/null 2>&1 end # Define the postexec function @@ -29,5 +31,5 @@ function fish_postexec --on-event fish_postexec return end # This event is triggered before each prompt, which is after each command - shelltime track -s=fish -id=$SESSION_ID -cmd="$argv" -p=post -r=$LAST_RESULT --ppid=$FISH_PPID > /dev/null + shelltime track -s=fish -id=$SESSION_ID -cmd="$argv" -p=post -r=$LAST_RESULT --ppid=$FISH_PPID < /dev/null > /dev/null 2>&1 end diff --git a/hooks/zsh.zsh b/hooks/zsh.zsh index fb89c05..828fb40 100644 --- a/hooks/zsh.zsh +++ b/hooks/zsh.zsh @@ -11,6 +11,11 @@ fi # Create a timestamp for the session when the shell starts SESSION_ID=$(date +%Y%m%d%H%M%S) +# Set by preexec and consumed by the next precmd. Empty means this prompt +# does not follow a command we tracked (shell startup, or enter on a blank +# line). fc -ln -1 would otherwise repeat the previous history entry. +typeset -g _SHELLTIME_LAST_CMD="" + # Define the preexec function preexec() { local CMD=$1 @@ -19,13 +24,18 @@ preexec() { return fi + _SHELLTIME_LAST_CMD=$CMD shelltime track -s=zsh -id=$SESSION_ID -cmd="$CMD" -p=pre --ppid=$PPID &> /dev/null } # Define the postexec function (in zsh, it's called precmd) precmd() { local LAST_RESULT=$? - local CMD=$(fc -ln -1) + local CMD=$_SHELLTIME_LAST_CMD + _SHELLTIME_LAST_CMD="" + if [[ -z $CMD ]]; then + return + fi # Check if command starts with exit, logout, or reboot if [[ $CMD =~ ^(exit|logout|reboot) ]]; then return diff --git a/install.bash b/install.bash index 09da575..a02e4fd 100644 --- a/install.bash +++ b/install.bash @@ -322,10 +322,19 @@ if command_exists shelltime; then fi fi +# The command that reloads the user's own shell ($SHELL is the login shell, also +# under `curl | bash`), so the next steps name one file instead of three. +case "$(basename "${SHELL:-}")" in + zsh) reload_cmd="source ~/.zshrc" ;; + bash) reload_cmd="source ~/.bashrc" ;; + fish) reload_cmd="source ~/.config/fish/config.fish" ;; + *) reload_cmd="source ~/.zshrc (or ~/.bashrc / ~/.config/fish/config.fish)" ;; +esac + echo "" echo "Installation complete!" echo "" echo "Next steps:" -echo " 1. Reload your shell: source ~/.zshrc (or ~/.bashrc / ~/.config/fish/config.fish)" +echo " 1. Open a new terminal, or reload this one: ${reload_cmd}" echo " 2. Run: shelltime init" echo ""