docs(phase-3): research hook server domain
This commit is contained in:
494
.planning/phases/03-hook-server/03-RESEARCH.md
Normal file
494
.planning/phases/03-hook-server/03-RESEARCH.md
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
# Phase 3: Hook Server - Research
|
||||||
|
|
||||||
|
**Researched:** 2026-03-23
|
||||||
|
**Domain:** Claude Code hooks HTTP, Go net/http server, state detection push
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Claude Code supporte nativement les hooks HTTP (type `"http"`) dans `settings.json`. vmuxd doit exposer un serveur HTTP local qui recoit les events POST de Claude Code. Chaque event contient `session_id`, `cwd`, `transcript_path`, et `hook_event_name`. Les events pertinents pour vmux sont : **Notification** (avec `notification_type` : `permission_prompt`, `idle_prompt`), **Stop** (session terminee), **PostToolUse** (session active), et **PreToolUse** (session active).
|
||||||
|
|
||||||
|
Le format est simple : Claude Code POST du JSON sur l'URL configuree, attend une reponse 2xx. Le serveur vmuxd parse le payload, met a jour le registre, et repond 200 OK. Pas besoin de lib externe, `net/http` stdlib suffit.
|
||||||
|
|
||||||
|
**Primary recommendation:** Ajouter un `http.Server` dans le Daemon qui ecoute sur `localhost:3119`, configurer les hooks HTTP dans `~/.claude/settings.json`, et mapper les events vers des mises a jour imm du registre avec le nouveau champ `WaitType`.
|
||||||
|
|
||||||
|
<user_constraints>
|
||||||
|
## User Constraints (from CONTEXT.md)
|
||||||
|
|
||||||
|
### Locked Decisions
|
||||||
|
- **D-01:** Hooks comme source primaire. Le poll reste actif mais ralenti (15-30s) comme filet de securite pour les sessions sans hooks configures ou en cas d'event rate.
|
||||||
|
- **D-02:** Quand un hook event arrive, le registre est mis a jour immediatement (pas besoin d'attendre le prochain cycle de poll).
|
||||||
|
|
||||||
|
### Claude's Discretion
|
||||||
|
- Port HTTP pour le hook server (ex: localhost:3119 ou port dynamique)
|
||||||
|
- Format exact des requetes hook Claude Code (consulter la doc officielle)
|
||||||
|
- Mapping events hook vers types d'attente (permission_prompt, idle_prompt, etc.)
|
||||||
|
- Comment les hooks Claude Code sont configures (fichier .claude/settings.json ou equivalent)
|
||||||
|
- Intervalle du poll fallback ralenti (entre 15s et 30s)
|
||||||
|
|
||||||
|
### Deferred Ideas (OUT OF SCOPE)
|
||||||
|
None
|
||||||
|
</user_constraints>
|
||||||
|
|
||||||
|
<phase_requirements>
|
||||||
|
## Phase Requirements
|
||||||
|
|
||||||
|
| ID | Description | Research Support |
|
||||||
|
|----|-------------|------------------|
|
||||||
|
| STATE-03 | vmux distingue le type d'attente (permission prompt, question utilisateur, idle prompt) | Les hooks Notification fournissent `notification_type` avec valeurs `permission_prompt` et `idle_prompt`. Le hook Stop + `last_assistant_message` permet de detecter `end_turn` (question utilisateur). PostToolUse confirme "Working". |
|
||||||
|
</phase_requirements>
|
||||||
|
|
||||||
|
## Standard Stack
|
||||||
|
|
||||||
|
### Core
|
||||||
|
| Library | Version | Purpose | Why Standard |
|
||||||
|
|---------|---------|---------|--------------|
|
||||||
|
| net/http (stdlib) | Go 1.25 | HTTP server | Suffisant pour un serveur local simple. Pas besoin de framework. |
|
||||||
|
| encoding/json (stdlib) | Go 1.25 | JSON parsing | Deja utilise partout dans le projet. |
|
||||||
|
|
||||||
|
### Supporting
|
||||||
|
| Library | Version | Purpose | When to Use |
|
||||||
|
|---------|---------|---------|-------------|
|
||||||
|
| net/http/httptest (stdlib) | Go 1.25 | Test HTTP handlers | Pour les tests unitaires du hook server sans ouvrir de port. |
|
||||||
|
|
||||||
|
### Alternatives Considered
|
||||||
|
| Recommended | Alternative | When to Use Alternative |
|
||||||
|
|-------------|-------------|-------------------------|
|
||||||
|
| net/http stdlib | chi / gin / echo | Jamais pour ce cas. Un seul endpoint POST, zero besoin de routing avance. |
|
||||||
|
| Port fixe 3119 | Port dynamique (port 0) | Si conflit de port. Mais le port dynamique compliquerait la configuration hooks dans settings.json (il faudrait un script qui decouvre le port). |
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### Hook Server Integration dans le Daemon
|
||||||
|
|
||||||
|
Le hook server est une goroutine supplementaire dans le Daemon existant, comme pollLoop et acceptLoop.
|
||||||
|
|
||||||
|
```
|
||||||
|
Daemon
|
||||||
|
|-- acceptLoop() (Unix socket, deja existant)
|
||||||
|
|-- pollLoop() (scan /proc, deja existant, sera ralenti)
|
||||||
|
|-- hookServerLoop() (NEW: HTTP server localhost:3119)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Payload Claude Code (verifie avec la doc officielle)
|
||||||
|
|
||||||
|
Tous les events recoivent ces champs communs :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"session_id": "abc123",
|
||||||
|
"transcript_path": "/home/pierre/.claude/projects/.../uuid.jsonl",
|
||||||
|
"cwd": "/home/pierre/Code/vibe/vmux",
|
||||||
|
"hook_event_name": "Notification"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notification** ajoute :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"notification_type": "permission_prompt",
|
||||||
|
"message": "Claude needs your permission to use Bash",
|
||||||
|
"title": "Permission needed"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Stop** ajoute :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"stop_hook_active": true,
|
||||||
|
"last_assistant_message": "I've completed..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**PostToolUse** ajoute :
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tool_name": "Bash",
|
||||||
|
"tool_input": { "command": "..." }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mapping Events vers WaitType
|
||||||
|
|
||||||
|
| Hook Event | notification_type | WaitType dans vmux | SessionState |
|
||||||
|
|------------|------------------|-------------------|--------------|
|
||||||
|
| Notification | `permission_prompt` | `"permission"` | NeedsInput |
|
||||||
|
| Notification | `idle_prompt` | `"idle"` | NeedsInput |
|
||||||
|
| Stop | - | `"question"` | NeedsInput |
|
||||||
|
| PostToolUse | - | `""` (clear) | Working |
|
||||||
|
| PreToolUse | - | `""` (clear) | Working |
|
||||||
|
|
||||||
|
Le champ `WaitType` est ajoute a `SessionInfo` :
|
||||||
|
|
||||||
|
```go
|
||||||
|
type SessionInfo struct {
|
||||||
|
// ... champs existants ...
|
||||||
|
WaitType string `json:"wait_type,omitempty"` // "permission", "question", "idle", ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration hooks dans settings.json
|
||||||
|
|
||||||
|
Ajouter dans `~/.claude/settings.json` :
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"Notification": [
|
||||||
|
{
|
||||||
|
"matcher": "permission_prompt|idle_prompt",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"url": "http://localhost:3119/hook",
|
||||||
|
"timeout": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Stop": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"url": "http://localhost:3119/hook",
|
||||||
|
"timeout": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"url": "http://localhost:3119/hook",
|
||||||
|
"timeout": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Un seul endpoint `/hook` suffit. Le `hook_event_name` dans le payload permet de router.
|
||||||
|
|
||||||
|
### Cohabitation Poll / Hooks (D-01)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Quand un hook event arrive, reset le timer de poll pour eviter le double scan
|
||||||
|
type Daemon struct {
|
||||||
|
// ... existant ...
|
||||||
|
hookPort int
|
||||||
|
httpServer *http.Server
|
||||||
|
pollInterval time.Duration // passe de 5s a 20s quand hooks actifs
|
||||||
|
lastHookTime time.Time // pour savoir si les hooks fonctionnent
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Strategie : si `lastHookTime` < 60s, le poll est en mode fallback (20s). Sinon, revenir au poll rapide (5s) pour couvrir les sessions sans hooks.
|
||||||
|
|
||||||
|
### Pattern du Handler HTTP
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d *Daemon) handleHook(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var event HookEvent
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
||||||
|
http.Error(w, "bad request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.processHookEvent(event)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns to Avoid
|
||||||
|
- **Over-engineering le routing**: Un seul endpoint `/hook` avec dispatch sur `hook_event_name`. Pas de router, pas de middleware.
|
||||||
|
- **Bloquer sur le hook**: Le handler doit repondre vite (< 100ms). La mise a jour du registre est un lock rapide, pas de I/O.
|
||||||
|
- **Ignorer le timeout**: Claude Code attend max 30s (defaut HTTP hooks). Repondre en < 100ms elimine ce risque.
|
||||||
|
|
||||||
|
## Don't Hand-Roll
|
||||||
|
|
||||||
|
| Problem | Don't Build | Use Instead | Why |
|
||||||
|
|---------|-------------|-------------|-----|
|
||||||
|
| HTTP server | Framework (chi, gin) | net/http stdlib | Un seul endpoint, zero routing complexe |
|
||||||
|
| JSON parsing du payload | Struct generique + reflection | Structs Go typees par event | Plus simple, plus rapide, testable |
|
||||||
|
| Settings.json generation | Script qui modifie settings.json | Documentation manuelle + `vmux setup` futur | Modifier settings.json d'un utilisateur est dangereux |
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Pitfall 1: Port deja occupe
|
||||||
|
**What goes wrong:** Le daemon ne demarre pas si le port 3119 est deja pris.
|
||||||
|
**Why it happens:** Autre instance de vmuxd, ou autre service local.
|
||||||
|
**How to avoid:** Essayer de bind, si erreur logger un warning et continuer sans hooks (fallback poll uniquement). Ne pas crasher le daemon.
|
||||||
|
**Warning signs:** `listen tcp 127.0.0.1:3119: bind: address already in use`
|
||||||
|
|
||||||
|
### Pitfall 2: Session ID mismatch entre hooks et poll
|
||||||
|
**What goes wrong:** Le hook envoie un `session_id` que le registre ne connait pas encore (session pas encore scannee par le poll).
|
||||||
|
**Why it happens:** Le hook arrive avant le premier scan du poll pour cette session.
|
||||||
|
**How to avoid:** Le hook handler doit creer une entree dans le registre meme si la session n'existe pas encore. Le poll la completera au prochain cycle.
|
||||||
|
**Warning signs:** Hook events ignores silencieusement.
|
||||||
|
|
||||||
|
### Pitfall 3: Hooks pas configures
|
||||||
|
**What goes wrong:** Le serveur HTTP tourne mais ne recoit aucun event.
|
||||||
|
**Why it happens:** L'utilisateur n'a pas ajoute les hooks dans `~/.claude/settings.json`.
|
||||||
|
**How to avoid:** Commande `vmux setup` ou documentation claire. Logger un warning si aucun hook recu apres 60s de fonctionnement avec des sessions actives.
|
||||||
|
**Warning signs:** `vmux list` montre des etats avec le delai du poll (5-20s) au lieu du temps reel.
|
||||||
|
|
||||||
|
### Pitfall 4: Hook settings.json ecrase les hooks existants
|
||||||
|
**What goes wrong:** L'utilisateur a deja des hooks PostToolUse (comme rtk-rewrite.sh, piaire-post.sh). Ajouter vmux ne doit pas les supprimer.
|
||||||
|
**Why it happens:** Chaque event dans settings.json est un tableau. vmux ajoute un element, pas un remplacement.
|
||||||
|
**How to avoid:** La config vmux s'ajoute au tableau existant. Le matcher `"*"` ou absent capte tous les events.
|
||||||
|
**Warning signs:** Les hooks existants (RTK, piaire) cessent de fonctionner.
|
||||||
|
|
||||||
|
### Pitfall 5: Body trop grand / request malformee
|
||||||
|
**What goes wrong:** Le handler panic ou bloque sur un body de taille illimitee.
|
||||||
|
**Why it happens:** Pas de limit reader sur le body.
|
||||||
|
**How to avoid:** `http.MaxBytesReader(w, r.Body, 64*1024)` (64KB largement suffisant).
|
||||||
|
**Warning signs:** Memory spike sur le daemon.
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
### HookEvent struct
|
||||||
|
|
||||||
|
```go
|
||||||
|
// HookEvent represents the JSON payload sent by Claude Code hooks.
|
||||||
|
type HookEvent struct {
|
||||||
|
SessionID string `json:"session_id"`
|
||||||
|
TranscriptPath string `json:"transcript_path"`
|
||||||
|
Cwd string `json:"cwd"`
|
||||||
|
HookEventName string `json:"hook_event_name"`
|
||||||
|
// Notification-specific
|
||||||
|
NotificationType string `json:"notification_type,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
// Stop-specific
|
||||||
|
LastAssistantMsg string `json:"last_assistant_message,omitempty"`
|
||||||
|
StopHookActive bool `json:"stop_hook_active,omitempty"`
|
||||||
|
// PostToolUse-specific
|
||||||
|
ToolName string `json:"tool_name,omitempty"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### processHookEvent
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d *Daemon) processHookEvent(event HookEvent) {
|
||||||
|
if event.SessionID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var state string
|
||||||
|
var waitType string
|
||||||
|
|
||||||
|
switch event.HookEventName {
|
||||||
|
case "Notification":
|
||||||
|
state = "Needs Input"
|
||||||
|
switch event.NotificationType {
|
||||||
|
case "permission_prompt":
|
||||||
|
waitType = "permission"
|
||||||
|
case "idle_prompt":
|
||||||
|
waitType = "idle"
|
||||||
|
default:
|
||||||
|
waitType = "question"
|
||||||
|
}
|
||||||
|
case "Stop":
|
||||||
|
state = "Needs Input"
|
||||||
|
waitType = "question"
|
||||||
|
case "PostToolUse", "PreToolUse":
|
||||||
|
state = "Working"
|
||||||
|
waitType = ""
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.registry.UpdateFromHook(event.SessionID, state, waitType, event.Cwd)
|
||||||
|
d.lastHookTime = time.Now()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registry.UpdateFromHook
|
||||||
|
|
||||||
|
```go
|
||||||
|
// UpdateFromHook updates a session from a hook event.
|
||||||
|
// Creates the entry if it doesn't exist yet (hook can arrive before first poll).
|
||||||
|
func (r *SessionRegistry) UpdateFromHook(sessionID, state, waitType, cwd string) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
existing, ok := r.sessions[sessionID]
|
||||||
|
if !ok {
|
||||||
|
existing = &TrackedSession{}
|
||||||
|
r.sessions[sessionID] = existing
|
||||||
|
}
|
||||||
|
|
||||||
|
prevState := existing.PrevState
|
||||||
|
existing.Info.SessionID = sessionID
|
||||||
|
existing.Info.State = state
|
||||||
|
existing.Info.WaitType = waitType
|
||||||
|
if cwd != "" {
|
||||||
|
existing.Info.Cwd = cwd
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitingSince transition
|
||||||
|
isWaiting := state == "Needs Input"
|
||||||
|
wasWaiting := prevState == "Needs Input"
|
||||||
|
if isWaiting && !wasWaiting {
|
||||||
|
now := time.Now()
|
||||||
|
existing.Info.WaitingSince = &now
|
||||||
|
} else if !isWaiting {
|
||||||
|
existing.Info.WaitingSince = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.PrevState = state
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTTP Server startup dans Daemon
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (d *Daemon) startHookServer() error {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/hook", d.handleHook)
|
||||||
|
|
||||||
|
d.httpServer = &http.Server{
|
||||||
|
Addr: fmt.Sprintf("127.0.0.1:%d", d.hookPort),
|
||||||
|
Handler: mux,
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", d.httpServer.Addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: hook server unavailable on port %d: %v (poll-only mode)", d.hookPort, err)
|
||||||
|
return nil // graceful degradation
|
||||||
|
}
|
||||||
|
|
||||||
|
go d.httpServer.Serve(ln)
|
||||||
|
log.Printf("Hook server listening on %s", d.httpServer.Addr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test pattern avec httptest
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestHandleHookNotification(t *testing.T) {
|
||||||
|
d := newTestDaemon(t)
|
||||||
|
d.hookPort = 0 // not used in direct handler test
|
||||||
|
|
||||||
|
body := `{
|
||||||
|
"session_id": "sess-1",
|
||||||
|
"hook_event_name": "Notification",
|
||||||
|
"notification_type": "permission_prompt",
|
||||||
|
"cwd": "/home/user/project"
|
||||||
|
}`
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/hook", strings.NewReader(body))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
d.handleHook(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want 200", w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
list := d.registry.List()
|
||||||
|
if len(list) != 1 {
|
||||||
|
t.Fatalf("registry len = %d, want 1", len(list))
|
||||||
|
}
|
||||||
|
if list[0].WaitType != "permission" {
|
||||||
|
t.Errorf("WaitType = %q, want %q", list[0].WaitType, "permission")
|
||||||
|
}
|
||||||
|
if list[0].State != "Needs Input" {
|
||||||
|
t.Errorf("State = %q, want %q", list[0].State, "Needs Input")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Architecture
|
||||||
|
|
||||||
|
### Test Framework
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Framework | Go testing (stdlib) |
|
||||||
|
| Config file | Makefile (target `test`) |
|
||||||
|
| Quick run command | `nix-shell --run "go test -v -run TestHook ./..."` |
|
||||||
|
| Full suite command | `nix-shell --run "go test -v -race ./..."` |
|
||||||
|
|
||||||
|
### Phase Requirements to Test Map
|
||||||
|
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|
||||||
|
|--------|----------|-----------|-------------------|-------------|
|
||||||
|
| STATE-03a | Hook event Notification/permission_prompt met a jour WaitType="permission" | unit | `nix-shell --run "go test -v -run TestHandleHookNotification ./..."` | Wave 0 |
|
||||||
|
| STATE-03b | Hook event Notification/idle_prompt met a jour WaitType="idle" | unit | `nix-shell --run "go test -v -run TestHandleHookIdle ./..."` | Wave 0 |
|
||||||
|
| STATE-03c | Hook event Stop met a jour WaitType="question" | unit | `nix-shell --run "go test -v -run TestHandleHookStop ./..."` | Wave 0 |
|
||||||
|
| STATE-03d | Hook event PostToolUse met a jour State="Working" et clear WaitType | unit | `nix-shell --run "go test -v -run TestHandleHookPostToolUse ./..."` | Wave 0 |
|
||||||
|
| STATE-03e | WaitType visible dans `vmux list` (SessionInfo JSON) | unit | `nix-shell --run "go test -v -run TestSessionInfoWaitType ./..."` | Wave 0 |
|
||||||
|
| STATE-03f | Poll ralenti a 20s quand hooks actifs | unit | `nix-shell --run "go test -v -run TestPollSlowdown ./..."` | Wave 0 |
|
||||||
|
| STATE-03g | Graceful degradation si port occupe | unit | `nix-shell --run "go test -v -run TestHookServerPortBusy ./..."` | Wave 0 |
|
||||||
|
|
||||||
|
### Sampling Rate
|
||||||
|
- **Per task commit:** `nix-shell --run "go test -v -run TestHook ./..."`
|
||||||
|
- **Per wave merge:** `nix-shell --run "go test -v -race ./..."`
|
||||||
|
- **Phase gate:** Full suite green before `/gsd:verify-work`
|
||||||
|
|
||||||
|
### Wave 0 Gaps
|
||||||
|
- [ ] `hook_test.go` -- tests du hook HTTP handler (STATE-03a/b/c/d)
|
||||||
|
- [ ] `protocol_test.go` -- test du champ WaitType dans SessionInfo (STATE-03e)
|
||||||
|
- [ ] `daemon_test.go` -- test poll slowdown et graceful degradation (STATE-03f/g)
|
||||||
|
|
||||||
|
## State of the Art
|
||||||
|
|
||||||
|
| Old Approach | Current Approach | When Changed | Impact |
|
||||||
|
|--------------|------------------|--------------|--------|
|
||||||
|
| Hooks command-only | Hooks HTTP natif | Claude Code 2025-Q4 | Plus besoin de scripts shell intermediaires |
|
||||||
|
| 12 hook events | 22 hook events (mars 2026) | Mars 2026 | SessionEnd, PreCompact, PostCompact, ConfigChange, etc. |
|
||||||
|
| Pas de notification_type | Matcher par notification_type | 2026 | Permet de filtrer permission_prompt vs idle_prompt |
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
1. **Faut-il un `vmux setup` qui modifie settings.json automatiquement ?**
|
||||||
|
- Ce qu'on sait : settings.json est un fichier partage avec les hooks existants (RTK, piaire, GSD). Le modifier programmatiquement risque d'ecraser des configurations.
|
||||||
|
- Recommandation : Phase 3 fournit la documentation de la configuration. Un `vmux setup` automatique peut etre ajoute plus tard (pas dans le scope STATE-03).
|
||||||
|
|
||||||
|
2. **Le `idle_prompt` se declenche quand exactement ?**
|
||||||
|
- Ce qu'on sait : quand Claude Code affiche le prompt apres avoir fini de repondre (l'utilisateur n'a pas encore tape de message). C'est equivalent au `end_turn` + idle.
|
||||||
|
- Ce qui n'est pas clair : la difference exacte entre `idle_prompt` et `Stop`. En pratique, `Stop` fire quand Claude finit de repondre, `idle_prompt` fire quand le prompt attend depuis un moment.
|
||||||
|
- Recommandation : traiter les deux. `Stop` = `"question"`, `idle_prompt` = `"idle"`.
|
||||||
|
|
||||||
|
## Environment Availability
|
||||||
|
|
||||||
|
| Dependency | Required By | Available | Version | Fallback |
|
||||||
|
|------------|------------|-----------|---------|----------|
|
||||||
|
| Go | Build | Oui (nix-shell) | 1.25.7 | -- |
|
||||||
|
| net/http (stdlib) | Hook server | Oui | Go 1.25 | -- |
|
||||||
|
| Claude Code hooks HTTP | Push events | Oui (settings.json) | 22 events (mars 2026) | Poll-only (5s) |
|
||||||
|
| Port 3119 TCP | Hook server | A verifier au runtime | -- | Log warning, poll-only |
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- [Claude Code Hooks Reference](https://code.claude.com/docs/en/hooks) - Documentation officielle complete des 22 events, format payload HTTP, configuration settings.json
|
||||||
|
- `~/.claude/settings.json` sur la machine locale - Verification directe du format existant des hooks command
|
||||||
|
- Code existant vmux (daemon.go, state.go, protocol.go) - Architecture actuelle du daemon
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- [Algo Insights - Claude Code HTTP Hooks](https://algoinsights.medium.com/claude-code-just-got-http-hooks-heres-why-that-changes-everything-6938ffaae1f6) - Confirmation des HTTP hooks
|
||||||
|
- Hooks existants (rtk-rewrite.sh, piaire-post.sh, gsd-context-monitor.js) - Format stdin/stdout des hooks command verifie, confirme les champs JSON
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
**Confidence breakdown:**
|
||||||
|
- Standard stack: HIGH - stdlib Go, pas de dependance externe
|
||||||
|
- Architecture: HIGH - Pattern hook HTTP verifie dans la doc officielle, integration daemon existante claire
|
||||||
|
- Pitfalls: HIGH - Verifies par inspection du code existant et des hooks deja configures
|
||||||
|
|
||||||
|
**Research date:** 2026-03-23
|
||||||
|
**Valid until:** 2026-04-23 (stable, pas de breaking changes attendus)
|
||||||
Reference in New Issue
Block a user