iTranslated by AI
Week 02: Eliminating 'Session Expiration' with Claude Code Hooks – Building a Foundation for Solo AI Development
Conclusion: Solving "Session Timeouts" with Automation
When conducting long-term development with Claude Code, the context window eventually overflows, triggering compression. Once compressed, the AI loses track of "what it was doing." This problem becomes even more severe when running multiple agents in parallel via Agent Teams.
In this article, I will introduce a system that uses Claude Code's hooks to automatically prevent context loss and establish a foundation for multiple agents to work in parallel safely.
What I built:
- 3 Hooks: Automatic state saving -> Automatic recovery -> Automatic change logging
- Git Branch Strategy: Isolate branches for each agent
- CLAUDE.md: A rule file that the AI reads automatically (standards for self-review, branch operations, etc.)
- Template + Initialization Script: Deploy to new projects with a single command
Everything is templated, so it can be reused in any project.
The Problem: Three Patterns of Context Loss
When developing with Claude Code, context is lost in the following patterns:
| Pattern | What happens | Impact |
|---|---|---|
| Context Compression | Automatically compressed during long conversations. The AI after compression doesn't know the immediate previous state | Requires manual explanation for handover |
| Session Timeout | Network disconnection, timeout, etc., breaks the session | Same as above. With Agent Teams, the context for all agents is lost |
| Resuming across days | No previous context when a new session is started | Requires explaining "this is where we left off" every time |
When using Agent Teams for solo development, the leader AI manages the worker AIs; if the leader's context is lost, the overall progress management collapses.
The traditional solution was to "manually write a handoff memo." However, manual work is forgettable, and the level of detail varies. I automated this using hooks.
Solution: Automating with 3 Hooks
Claude Code has a mechanism called hooks. You can associate shell scripts with specific events (context compression, session start, response completion, etc.) for automatic execution.
Configuration
.claude/
├── settings.json # Hook definitions
├── CLAUDE.md # Rules the AI reads automatically
├── hooks/
│ ├── pre-compact.sh # Before compression: Auto-save state
│ ├── on-session-start.sh # After compression: Auto-restore state
│ └── on-stop.sh # After response: Auto-log changes
└── state/
├── current-task.md # Working state snapshot (auto-updated)
├── session-log.md # Change history (auto-updated)
└── progress.md # Phase progress (manual + automatic)
settings.json
{
"hooks": {
"PreCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/pre-compact.sh"
}
]
}
],
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/on-session-start.sh"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/on-stop.sh"
}
]
}
]
}
}
Hook 1: pre-compact.sh — Auto-saving State Before Compression
Immediately before context compression, the following information is written to .claude/state/current-task.md.
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
STATE_DIR="$PROJECT_ROOT/.claude/state"
STATE_FILE="$STATE_DIR/current-task.md"
PROGRESS_FILE="$STATE_DIR/progress.md"
mkdir -p "$STATE_DIR"
{
echo "# Working State Snapshot"
echo "Save time: $(date '+%Y-%m-%d %H:%M:%S')"
# Recent commits
echo "## Recent Commits"
echo '```'
cd "$PROJECT_ROOT" && git log --oneline -5 2>/dev/null || echo "(Failed to retrieve)"
echo '```'
# Uncommitted changes
echo "## Uncommitted Changes"
echo '```'
cd "$PROJECT_ROOT" && git diff --stat 2>/dev/null || echo "(No changes)"
echo '```'
# Phase progress
if [ -f "$PROGRESS_FILE" ]; then
echo "## Phase Progress"
cat "$PROGRESS_FILE"
fi
# Recent session history
SESSION_LOG="$STATE_DIR/session-log.md"
if [ -f "$SESSION_LOG" ]; then
echo "## Recent Session History"
tail -30 "$SESSION_LOG"
fi
} > "$STATE_FILE"
Saved content:
- History of the last 5 commits
- Uncommitted changes (staged + unstaged)
- Untracked files
- Phase progress (content of progress.md)
- Recent session history (end of session-log.md)
This automatically snapshots "what I was doing," "how far I got," and "what is currently being changed."
Hook 2: on-session-start.sh — Automatic Restoration After Compression
When a session is resumed after compression, the content of the saved current-task.md is output to stdout. Since Claude Code automatically injects the stdout of the SessionStart hook into the AI's context, the AI resumes with awareness of the previous working state after compression.
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
STATE_FILE="$PROJECT_ROOT/.claude/state/current-task.md"
if [ -f "$STATE_FILE" ]; then
echo "=== Recovery Information After Context Compression ==="
cat "$STATE_FILE"
echo "=== End of Recovery Information ==="
echo "The above is the working state automatically saved before compression. Please continue your work based on this information."
else
echo "Note: Previous working state could not be restored."
fi
Point: The SessionStart matcher is set to "compact". This ensures it does not run during a normal session start, injecting state only when resuming after compression.
Hook 3: on-stop.sh — Automatic Change Logging per Response
Every time the AI's response completes, it checks git diff --stat, and if there are changes, appends them to session-log.md with a timestamp.
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
STATE_DIR="$PROJECT_ROOT/.claude/state"
SESSION_LOG="$STATE_DIR/session-log.md"
LAST_HASH_FILE="$STATE_DIR/.last-diff-hash"
mkdir -p "$STATE_DIR"
cd "$PROJECT_ROOT"
DIFF_STAT=$(git diff --stat 2>/dev/null)
CACHED_STAT=$(git diff --cached --stat 2>/dev/null)
# Skip if there are no changes
if [ -z "$DIFF_STAT" ] && [ -z "$CACHED_STAT" ]; then
exit 0
fi
# Skip if the diff is the same as the previous one (prevents duplicate logs)
CURRENT_HASH=$(echo "$DIFF_STAT$CACHED_STAT" | md5 2>/dev/null \
|| echo "$DIFF_STAT$CACHED_STAT" | md5sum 2>/dev/null | cut -d' ' -f1)
if [ -f "$LAST_HASH_FILE" ] && [ "$(cat "$LAST_HASH_FILE")" = "$CURRENT_HASH" ]; then
exit 0
fi
echo "$CURRENT_HASH" > "$LAST_HASH_FILE"
# Append to session log
{
echo "### $(date '+%Y-%m-%d %H:%M:%S')"
[ -n "$CACHED_STAT" ] && echo '**Staged:**' && echo '```' && echo "$CACHED_STAT" && echo '```'
[ -n "$DIFF_STAT" ] && echo '**Unstaged:**' && echo '```' && echo "$DIFF_STAT" && echo '```'
echo ""
} >> "$SESSION_LOG"
Duplicate Prevention: The hash of the diff is maintained in .last-diff-hash, and it will not record if the diff is identical to the previous one. This prevents the log from becoming bloated when the AI repeatedly touches the same file.
Branch Strategy: Preventing Inter-Agent Conflicts
While hooks solved the context management issue, there is another problem: the risk of conflict where multiple agents edit the same file simultaneously.
The solution is simple: isolate agent work areas using Git branches.
main (stable)
├── phase-0 <- Phase 0: 1 person working
├── phase-1a <- Phase 1: 2 people in parallel
├── phase-2a/auth <- Phase 2: 4 people in parallel
├── phase-2b/home
├── phase-2c/mypage
└── phase-2d/item
Rules:
- Each agent works only in their own branch
- Do not touch other agents' files
- Phase completion -> Self-review -> Merge to main (human approves)
By separating branches, even if one agent goes off the rails, it won't affect other agents' work. In the worst-case scenario, you can simply discard that branch.
CLAUDE.md: Rule File Automatically Read by AI
Even if you decide on hooks and a branch strategy, it's meaningless if the AI doesn't know the rules.
Claude Code automatically reads .claude/CLAUDE.md at the start of a session. If you write your rules here, the AI will operate according to them without needing instructions.
Contents:
- Self-review checklist (data layer / screen implementation / final check)
- Git branch operation rules
- Leader/worker role distribution for multi-agent development
-
progress.mdupdate rules - Commit/deploy rules
- The principle of "do not implement based on speculation if not written in the design document"
This ensures that even when a new session is started, the AI understands the "rules of development" from the beginning.
Templating: Deploying to New Projects with One Command
It would be a waste to keep these mechanisms project-specific. I templated them to be reusable in any project.
_templates/
├── init-claude.sh # Initialization script
└── claude-hooks/
├── CLAUDE.md # Development rule template
├── settings.json # Hooks configuration
└── hooks/
├── pre-compact.sh
├── on-session-start.sh
└── on-stop.sh
Deployment to a new project:
bash ~/work/Apps/_templates/init-claude.sh ~/work/Apps/NewProject
This simply:
- Copies the 3 scripts to
.claude/hooks/ - Generates
.claude/CLAUDE.md(AI automatically reads it) - Creates
.claude/settings.json(merges hooks if one already exists) - Creates
.claude/state/ - Adds
.claude/state/to.gitignore
It also safely merges only the hooks portion in projects where settings.json already exists (e.g., with permissions set).
Result of Actual Use
I started using this foundation the day after building it, and the difference was clear.
Before:
- Manually explain "what I was doing" every time compression runs
- Write a handoff memo when the session cuts out (often forgetting to do so)
- Trace back git logs when writing daily reports, asking "what did I do today?"
After:
- AI automatically understands and resumes the previous state even when compression runs
- Session timeouts are the same. Resume with just a word, "continue the work."
- Since change history is automatically accumulated in
session-log.md, for daily reports, I just ask the AI to "summarize based on this."
Pitfalls and Points to Note
Hooks are project-specific
Hooks written in .claude/settings.json only work when Claude Code is launched in that project directory. If you write them in a global configuration (~/.claude/settings.json), they will work in all projects, but the script placement must also be aligned. Managing it via templates + initialization scripts is more reliable.
CLAUDE.md is for rules, not memory
What should be written in CLAUDE.md are the rules for "how to develop." The working state of "what I am doing now" is the role of current-task.md managed by the hooks. Mixing these two will cause CLAUDE.md to bloat, burying the actual rules.
Clear session-log.md periodically
The Stop hook appends to the log every time there is a response, so it will bloat if left alone. It is better to clear it when you write your daily report.
Summary
| Mechanism | Solved Problem |
|---|---|
| PreCompact hook | Automatically save working state before context compression |
| SessionStart hook | Automatically restore state after compression. Resume with "continue" |
| Stop hook | Automatically log changes per response. Materials for daily reports |
| Git branch isolation | Prevent work conflicts between multiple agents |
| CLAUDE.md | AI automatically reads rules at session start |
| Template + init script | Deploy to new projects with 1 command |
Although Claude Code's hooks are only briefly introduced in the official documentation, they are a very powerful mechanism in the context of solo development × AI agents. You can squash the structural weakness of "AI development"—context loss—with mechanics.
The source code has been extracted as a template, but it is not currently public. Please build it for your own environment based on the structure and script examples in this article.
Discussion