@fukukei23

Practical Techniques for Automatically Configuring Claude Code CLI with SessionStart Hooks

  • AI
  • VibeCoding
  • LLM
0
Last updated at 2026-11-10Posted at 2026-11-10

Introduction

Claude Code CLI has hooks (SessionStart hooks) that run automatically when a session starts. This mechanism can be used to automate tasks such as launching a proxy, synchronizing secrets, and performing security checks when a session starts.

This article presents insights gained from operating 11 SessionStart hooks and records of solutions to the problems encountered.

What Are SessionStart Hooks?

You can register commands to run when a session starts in the Claude Code configuration file (~/.claude/settings.json).

{ "hooks": { "SessionStart": [ { "type": "command", "command": "bash /path/to/script.sh" } ] } }

When Claude Code starts, the registered scripts are executed in order. stdout is sent to the Claude Code context and displayed in the terminal via /dev/tty or >&2.

Hook Configuration (11 hooks)

~/.claude/scripts/ ├ llm/ │ └ start-glm-proxy.sh # Launch GLM Rate Proxy ├ config/ │ └ sync-secrets-to-settings.sh # Sync secrets ├ security/ │ └ check-command-safety.sh # Command safety check ├ session/ │ ├── load-handoff.sh # Load previous session information │ ├── load-obsidian-log.sh # Load SSOT journal │ ├── startup-banner.sh # Generate startup banner │ ├── generate-handoff.sh # Generate handoff information │ └ save-session-log.sh # Save session log ├ obsidian/ │ └ check-mcp-guide-diff.sh # Detect MCP configuration differences ├ git/ │ └ check-submodules.sh # Check submodule status └ app/ └ update-indexes.sh # Update indexes

The role of each hook is explained below.

1. Launching the GLM Rate Proxy (start-glm-proxy.sh)

Launches the local proxy that serves as the core of LLM routing.

Processing details:

  1. Check whether it is already running (pgrep + health check)
  2. If it is running, verify ANTHROPIC_BASE_URL in settings.json
  3. If it is not running, launch the proxy in the background
  4. If startup fails, fall back to the direct Z.AI URL
ensure_settings_url() { if [ -f "$SETTINGS" ]; thensed -i "s|\"ANTHROPIC_BASE_URL\": \"[^\"]*\"|\"ANTHROPIC_BASE_URL\": \"$1\"|" "$SETTINGS" fi }

The important point is that URL validation is performed even when the proxy is healthy. When settings.json is reset, such as during a PC migration, there was a problem where Claude Code would connect directly to Z.AI even while the proxy was running.

2. Secret synchronization (sync-secrets-to-settings.sh)

Loads environment variables from ~/.secrets.env and synchronizes them with the env section of settings.json. This is a mechanism that injects API keys via environment variables instead of writing them directly into the file.

3. Handoff loading (load-handoff.sh)

Loads handoff.md, which was generated at the end of the previous session, and sends it into Claude Code's context.

if [[ -f "$HANDOFF_FILE" ]]; then echo "--- Handoff ---" cat "$HANDOFF_FILE" echo "--- /Handoff ---" fi

This allows Claude Code in a new session to carry over the work from the previous session.

4. Startup banner (startup-banner.sh)

Aggregates the status of each hook and displays it as a single banner in the terminal.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🚀 Claude Code session started ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✅ Secrets: 6/6 loaded ✅ GLM Proxy: healthy ✅ Security: active ✅ MCP Guide: synced ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📋 Previous: NexusCore refactoring complete 📝 3 sessions today | Last: README cleanup (ended at 12:27) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Issues Encountered and Solutions

Problem 1: The banner is not displayed

Symptom: When launching claude in a new terminal, the startup banner sometimes may not be displayed.

Cause: The shell within the SessionStart hookis executed as anon-interactive shell

, so output to /dev/tty or >&2 may not reach the terminal. Depending on the terminal's state or startup method, the ability to connect to /dev/tty may vary, resulting in the banner "sometimes appearing and sometimes not."

Solution: I moved the banner output to the claude() shell function in .bashrc (the interactive shell side). The SessionStart hook now only writes the result to a status file (/tmp/claude-startup/*.status), and .bashrc reads and displays that file.

Hook: Write status file (reliable) ↓ .bashrc claude(): Read status file → Display banner (reliable)

Problem 2: Fallback does not work even when the proxy is running

Symptom: After migrating to a new PC, the GLM→MiniMax fallback was not working.

Cause: When the proxy was already running, the health check exited with code 0 immediately after returning OK, without validating ANTHROPIC_BASE_URL in settings.json. The PC migration reset the settings, resulting in a URL that bypassed the proxy.

Solution: I changed it to call ensure_settings_url() in all three paths (healthy / successful startup / failed startup).

# Validate the URL even when healthy if pgrep -f "python3 -m glm_rate_proxy" > /dev/null 2>&1; thenif curl -sf -m 2 http://127.0.0.1:8787/proxy/status > /dev/null 2>&1; then ensure_settings_url "$PROXY_URL" # ← Added exit 0 fi fi

Problem 3: Regenerating MCP settings through secret synchronization

Symptom: Even if an MCP server is deleted, it is restored when the next session begins.

Cause: When sync-secrets-to-settings.sh synchronizes environment variables to settings.json, it regenerates entries for nonexistent MCP servers with null values.

Solution: Removed unnecessary jq entries for MCP servers from the script.

Hook design principles

1. Make each hook independent

It is important not to create dependencies between hooks. Ensure that the other hooks still run even if one hook fails.

2. Communicate status through files

Hook execution results are written to a status file under /tmp/claude-startup/. This makes them easier to reference from other hooks and from .bashrc.

3. Use stdout and /dev/tty appropriately

  • stdout (default): Sent to Claude Code's context. Information read by Claude
  • /dev/tty: Displayed directly in the terminal. Information seen by the user
  • >&2 (stderr): Absorbed by the Claude Code process, but serves as a fallback when /dev/tty is unavailable

4. Incorporate fail-safes

For example, when the proxy fails to start, fall back to the direct Z.AI URL, so Claude Code itself remains usable even if a hook fails.

Checklist for migrating to a new PC

These are the minimum items that should be checked when setting up Claude Code in a new PC environment.

  1. Whether API keys are configured in ~/.secrets.env
  2. Check whether an ANTHROPIC_BASE_URL entry exists in settings.json
  3. Run start-glm-proxy.sh manually once to verify that it works
  4. grep "ANTHROPIC_BASE_URL" ~/.claude/settings.json → It is the proxy URL
  5. curl -s http://127.0.0.1:8787/proxy/status → Responds normally

Limitations of this configuration

  • WSL2 only: Because the behavior of /dev/tty depends on the environment, adjustments are required in other environments
  • Increased number of hooks: Since 11 hooks run sequentially, startup takes several seconds
  • Managing two configuration file locations: You need to be aware of both ~/.claude.json and ~/.claude/settings.json

Conclusion

The SessionStart hook is a powerful mechanism for adapting the Claude Code CLI to your own development workflow. By automating proxy startup, secret management, session continuation, and more, it can eliminate manual work when starting a session.

However, because it runs in a non-interactive shell, measures such as displaying information intended for the user on the .bashrc side are necessary. When designing hooks, keeping three points in mind—“independence,” “fail-safe behavior,” and “separate use of stdout/tty”—will result in a stable configuration.

Related articles

This article is also published onpersonal Zenn.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
Sign up Login