Overview
AIFlare combines Claude Code Hooks (automatic triggers) and Skills (agent execution) to automatically capture AI agent work context. From session start to code push, it records conversations, commit intent, and group relations — and all capture actions continue gracefully even if they fail.
Session ID Routing
When a Claude Code session starts or resumes, the SessionStart Hook runs. No server call is made at this point.
The hook appends the Claude Code-assigned session_id to `$CLAUDE_ENV_FILE` as `export CLAUDE_SESSION_ID='<id>'`. From then on, every Bash child process spawned in this session inherits the same session ID as an environment variable.
When parallel Claude Code sessions run in the same working directory, this is the key signal that lets capture.js deterministically identify which session it belongs to. Without the env var, mtime-based heuristics could pick a file from a different session and misroute the capture.
# Claude Code 세션 시작 / resume SessionStart Hook 실행 → echo "export CLAUDE_SESSION_ID='<session_id>'" >> $CLAUDE_ENV_FILE → 이후 모든 Bash 자식 프로세스가 CLAUDE_SESSION_ID 상속 → capture.js 가 환경 변수로 세션을 결정적으로 식별
No WorkSession record is created at this point. It is lazily created on the first commit via the PUT /api/v1/work-sessions/prompt call. When the session ends (SessionEnd Hook), local temp files in `.context-capture/` (accumulated conversation, offset, delta, pending-question) are cleaned up (no server call).
Conversation Capture
Automatically records both user prompts and AI responses.
The UserPromptSubmit Hook saves user input, and the Stop Hook saves AI responses — both appended in JSONL format to `.context-capture/.claude-prompts-{SESSION_ID}`.
Every time the AI agent completes a response, the Stop Hook runs and appends the response text to the same file. If `stop_hook_active=true`, it exits immediately to prevent infinite loops.
{"role":"user","content":"설정 변경해줘"} {"role":"assistant","content":"설정을 변경했습니다. config.yml의 timeout 값을..."} {"role":"user","content":"테스트도 추가해"} {"role":"assistant","content":"테스트를 추가했습니다. ConfigServiceTest에..."}
The file name includes the session ID, so conversations are separated by session. On commit, this log is delta-extracted and shown as 'Related Prompts' on the dashboard.
AskUserQuestion Signal
Right after Claude Code's AskUserQuestion built-in tool runs, the PostToolUse(matcher="AskUserQuestion") Hook touches an empty flag file.
It creates `.context-capture/.pending-question-{SESSION_ID}`. On the next commit, capture.js reads it, converts it into `continuation: true` in the payload, and deletes the file.
When an agent uses AskUserQuestion to ask the user, gets an answer, and commits afterward, the user's answer ends up in the delta and looks like 'new user input'. This flag lets the server still group it with the previous commit instead of starting a new group.
Limitations
- •If the agent asks in plain text (without the AskUserQuestion tool), there is no signal — the follow-up commit may split into a new group.
- •Agents without AskUserQuestion (Gemini CLI, Codex, etc.) always send continuation=false.
Post-Commit Processing
Right after git commit completes, the PostToolUse(Bash, if="Bash(git commit:*)") Hook performs three roles.
- 1.Session conversation upload — sends the full accumulated JSONL to PUT /api/v1/work-sessions/prompt (5s timeout). The WorkSession is lazily created at this point if it doesn't exist yet.
- 2.Delta extraction — extracts only the new conversation lines since the last capture (line-count indexing), writes them to .claude-conversation-delta-{SESSION_ID}, and updates the offset file.
- 3.Skill invocation — emits a hookSpecificOutput message that ensures the context-capture Skill runs.
# 대화 파일 (.claude-prompts-{SESSION_ID}) — 6줄 누적 Line 1-2: 첫 번째 작업 대화 (user + assistant) Line 3-4: 두 번째 작업 대화 (user + assistant) # ── 첫 번째 커밋 → delta: Line 1-4 (전체) ── Line 5-6: 세 번째 작업 대화 (user + assistant) # ── 두 번째 커밋 → delta: Line 5-6 (새 대화만) ──
At each commit point, only conversations that occurred after the previous capture are precisely extracted and saved as the commit's EntryConversation.
Context Capture
The context-capture Skill activates right after the commit. The agent analyzes its own conversation context to generate the summary data and runs capture.js.
capture.js takes the metadata arguments, resolves the session ID in order (`--claude-session-id` → `$CLAUDE_SESSION_ID` → mtime heuristic), and if `--conversation-snippet` is omitted, automatically reads the delta file and includes it before calling POST /api/v1/captures.
node .claude/skills/context-capture/scripts/capture.js \ --title "Getting Started 가이드 한국어 번역 키 추가" \ --intent "ko.json에 docs.gettingStarted 키 추가하여 i18n 지원" \ --commit-hash "e395786" \ --agent-type "CLAUDE_CODE" \ --changed-files "ko.json" \ --tag "FEATURE" # conversationSnippet 은 .claude-conversation-delta-<id> 에서 자동 추출 # continuation 은 .pending-question-<id> 존재 여부로 자동 계산 # claudeSessionId 는 $CLAUDE_SESSION_ID 환경 변수에서 자동 결정
Capture Data Fields
- •title — Work title (under 50 characters)
- •intent — Why this change was needed (Problem → Solution → Effect)
- •alternatives — Alternatives considered but not chosen
- •diffSummary — Summary of key changes
- •tag — Work type (FEATURE, BUGFIX, REFACTORING, TEST, DOCS)
- •changedFiles — List of changed files
- •conversationSnippet — User/AI conversation snippet auto-extracted from the delta file
- •continuation — Group continuity signal auto-computed from the .pending-question flag
Group Resolution (Server Side)
Inside POST /api/v1/captures, CaptureService.resolveGroupRootId groups consecutive commits that stem from a single user request.
Represented by a nullable self-reference column timeline_entries.group_root_id. NULL means root or standalone; non-null means this entry belongs to the root with that ID. Frontends use COALESCE(groupRootId, id) as the group key.
Decision Rules
| Previous entry | delta has user message | continuation | Decision |
|---|---|---|---|
| None | — | — | New root |
| Exists | false | — | Same group (agent-only consecutive commit) |
| Exists | true | true | Same group (AskUserQuestion follow-up) |
| Exists | true | false | New root (user started a new request) |
# (A) Agent 단독 연속 커밋 T0 User: "A, B, C 해줘" T1 commit A → delta has user → 새 root (101, root=NULL) T2 commit B → delta no user → 같은 그룹 (102, root=101) T3 commit C → delta no user → 같은 그룹 (103, root=101) # (B) AskUserQuestion 중간 개입 T0 User: "리팩토링 해줘" T1 commit part1 → 새 root (201, root=NULL) T2 Agent AskUserQuestion → .pending-question 생성 T3 User: "B 방식으로" T4 commit part2 → continuation=true → 같은 그룹 (202, root=201) # (C) 새 사용자 요청 T0 User: "A 해줘" → commit (301, root=NULL) T1 User: "이제 B 해줘" (평문) → commit continuation=false → 새 root (302, root=NULL)
Push-Time Sync
When you run git push, .git/hooks/pre-push runs automatically and forwards locally captured entries to the backend.
It extracts the list of commit hashes via `git log <remote>..<local> --format=%H`, then runs sync-captures.js (POST /api/v1/captures/batch with each entry from the local aiflare/timeline/v1 branch) and sync-sessions.js (PUT /api/v1/work-sessions/prompt with redacted session transcripts).
git push → pre-push hook 발동 (.git/hooks/pre-push) → git log <remote>..<local> --format=%H 로 커밋 해시 추출 → node sync-captures.js <hashes...> → aiflare/timeline/v1:entries/<hash>.json 읽기 → POST /api/v1/captures/batch → node sync-sessions.js <hashes...> → 푸시된 엔트리의 claudeSessionId 수집 (dedup) → aiflare/sessions/v1:sessions/<id>.jsonl 읽기 → 레다크션 → PUT /api/v1/work-sessions/prompt
Sync Pipeline
- •sync-captures.js — reads entries/<hash>.json from the aiflare/timeline/v1 branch for each pushed commit and batch-POSTs them to /captures/batch
- •sync-sessions.js — collects the distinct claudeSessionIds of those entries, reads sessions/<id>.jsonl from aiflare/sessions/v1, redacts secrets, and PUTs each session to /work-sessions/prompt
- •Local branches (aiflare/timeline/v1, aiflare/sessions/v1) are unconditionally rejected by pre-push — they never leave the machine via git
If the hook fails, the push itself is never blocked. When pre-push isn't installed or a sync call fails, entries simply remain on the local aiflare/timeline/v1 branch until a later sync succeeds.
Results on Dashboard
All the steps above combine to create a meaningful timeline on the AIFlare dashboard.
Captures linked by the same work session (claudeSessionId) appear as a single session group, so all commits from one work session show up together at a glance.
Consecutive commits sharing the same group_root_id are visually nested under the root entry — agent-only consecutive commits and AskUserQuestion follow-up commits both show up as a single bundle.
Each entry's 'Related Prompts' panel parses the delta JSONL stored in EntryConversation and renders it split by role (user / assistant).
Timeline Display Example