18 KiB
Phase 1: Session Discovery - Research
Researched: 2026-03-23 Domain: Linux process inspection + Claude Code JSONL parsing Confidence: HIGH
Summary
La Phase 1 est un CLI one-shot (vmux list) qui scanne les processus Claude Code actifs via /proc, croise les PID avec les fichiers JSONL dans ~/.claude/projects/, et affiche l'etat de chaque session.
Le format JSONL de Claude Code est bien documente et stable. Chaque message contient des metadonnees riches (sessionId, cwd, gitBranch, version). L'heuristique d'etat se base sur le stop_reason du dernier message assistant et le nom de l'outil si stop_reason=tool_use.
Decouverte critique : sessions-index.json n'est plus genere par les versions recentes de Claude Code (derniere MAJ: 2026-02-03). Seulement 13 des 91 dossiers projets en ont un. Il faut lire les metadonnees directement depuis le JSONL (chaque entree contient sessionId, cwd, gitBranch, version).
Recommandation principale : Ne pas dependre de sessions-index.json. Parser la premiere et la derniere entree du JSONL pour obtenir toutes les informations necessaires.
<user_constraints>
User Constraints (from CONTEXT.md)
Locked Decisions
- D-01: Scanner
/procpour trouver les PID des processus Claude Code actifs, puis enrichir avec les fichiers JSONL dans~/.claude/projects/. Un JSONL sans PID correspondant = session terminee, ignoree. - D-02: Utiliser
sessions-index.json(present dans chaque dossier projet sous~/.claude/projects/) comme source de metadonnees riches (sessionId, gitBranch, projectPath, summary, created, modified). Format verifie sur le poste. - D-03: Le matching PID -> session se fait via le cwd du processus (
/proc/PID/cwd-> readlink) croise avec leprojectPathdu sessions-index. - D-04: Se baser uniquement sur le JSONL (tail-read de la derniere entree) pour determiner l'etat. Pas de lecture CPU en Phase 1.
- D-05: Heuristique d'etat basee sur le dernier message JSONL (voir details dans CONTEXT.md).
- D-06: Les JSONL peuvent faire > 100 Mo. Toujours tail-read, jamais charger le fichier entier.
- D-07: Couleurs activees par defaut pour l'etat (vert = Working, rouge/jaune = Needs Input, gris = Idle).
Claude's Discretion
- Format d'affichage : tableau compact ou blocs detailles, libre de proposer un format hybride.
- Gestion du
--no-colorpour la compatibilite pipe.
Deferred Ideas (OUT OF SCOPE)
None </user_constraints>
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| DISC-01 | vmux detecte automatiquement les processus Claude Code actifs sur le poste | Scan /proc/*/cmdline pour trouver les processus claude. Verifie: 6 processus actifs trouves. |
| DISC-02 | vmux identifie le cwd et le worktree git de chaque session | readlink /proc/PID/cwd donne le cwd. Worktree git via git rev-parse --show-toplevel ou parsing .git. |
| DISC-03 | vmux affiche le nom de la branche git de chaque session | Champ gitBranch present dans chaque entree JSONL. Alternative: git -C <cwd> branch --show-current. |
| STATE-01 | vmux detecte l'etat de chaque session : travaille / attend input / idle | Heuristique basee sur stop_reason + content[].type du dernier message assistant. Voir section "Architecture Patterns". |
| STATE-02 | vmux affiche un apercu des dernieres lignes de sortie de chaque session | Champ content[].text du dernier message assistant type text. Aussi summary dans sessions-index.json quand disponible. |
| </phase_requirements> |
Standard Stack
Core
| Library | Version | Purpose | Why Standard |
|---|---|---|---|
| Go | 1.25.7 | Langage | Deja utilise par piaire. Disponible via nix-shell -p go. |
| Go stdlib (os, encoding/json, path/filepath, fmt) | - | Tout le runtime | Aucune dependance externe necessaire pour Phase 1. /proc se lit avec os.ReadFile/os.Readlink. |
Supporting
| Library | Version | Purpose | When to Use |
|---|---|---|---|
| Aucune | - | - | Phase 1 = stdlib uniquement. Pas de CLI framework, pas de TUI, pas de deps. |
Alternatives Considered
| Instead of | Could Use | Tradeoff |
|---|---|---|
| stdlib os pour /proc | prometheus/procfs | Over-engineered pour lire cmdline + cwd. API instable (WARNING dans leur doc). |
| stdlib flag | cobra/urfave | Over-engineered pour une seule commande list. flag suffit. |
| Reverse-read maison | hpcloud/tail | tail est pour le mode follow (Phase 2+). Pour un one-shot, un seek+read depuis la fin suffit. |
Installation:
# Rien a installer. Go stdlib uniquement.
nix-shell -p go --run "go mod init github.com/pieMusic/vmux"
Architecture Patterns
Recommended Project Structure
vmux/
├── main.go # Point d'entree, parsing des args
├── proc.go # Scan /proc pour les processus Claude
├── proc_test.go
├── session.go # Lecture JSONL + matching PID->session
├── session_test.go
├── state.go # Heuristique d'etat (Working/NeedsInput/Idle)
├── state_test.go
├── display.go # Formatage et affichage (couleurs, layout)
├── display_test.go
├── go.mod
└── shell.nix # Environnement de dev NixOS
Pattern 1: Scan /proc pour les processus Claude
What: Lister tous les PID dont /proc/PID/cmdline contient claude.
When to use: Au demarrage de vmux list.
Verification sur machine: Les processus Claude Code apparaissent comme claude ou claude -c dans cmdline.
// Verifie sur la machine : /proc/PID/cmdline contient "claude\x00" ou "claude\x00-c\x00"
func findClaudeProcesses() ([]Process, error) {
entries, err := os.ReadDir("/proc")
// Pour chaque entree numerique, lire /proc/PID/cmdline
// Si le premier argument est "claude", c'est un processus Claude Code
// Lire aussi /proc/PID/cwd via os.Readlink pour obtenir le cwd
}
Pattern 2: Encoding du chemin projet
What: Convertir un cwd en nom de dossier ~/.claude/projects/.
Verification: /home/pierre/Code/vibe/vmux -> -home-pierre-Code-vibe-vmux. Les / et . sont remplaces par -.
func encodePath(cwd string) string {
// Remplacer / et . par -
result := strings.ReplaceAll(cwd, "/", "-")
result = strings.ReplaceAll(result, ".", "-")
return result
}
Pattern 3: Tail-read JSONL (lecture depuis la fin)
What: Lire les N dernieres lignes d'un JSONL sans charger le fichier entier. When to use: Pour chaque session, lire la derniere entree pour determiner l'etat.
func tailReadJSONL(path string, n int) ([]json.RawMessage, error) {
f, err := os.Open(path)
// Seek vers la fin du fichier
// Lire des blocs de ~8KB en arriere jusqu'a trouver n lignes completes
// Parser chaque ligne comme JSON
}
Pattern 4: Heuristique d'etat
What: Determiner l'etat d'une session a partir du dernier message JSONL. Verification sur machine (resultats reels) :
| Dernier message | stop_reason | content_types | Etat |
|---|---|---|---|
type=assistant |
end_turn |
['text'] |
Needs Input (Claude a fini, attend l'utilisateur) |
type=assistant |
tool_use |
['tool_use'] avec name=AskUserQuestion |
Needs Input (Claude pose une question) |
type=assistant |
tool_use |
['tool_use'] avec autre outil |
Working (outil en cours d'execution) |
type=progress |
- | data.type=agent_progress |
Working (subagent actif) |
type=progress |
- | data.type=hook_progress |
Working (hook en cours) |
type=user avec content[].type=tool_result |
- | - | Working (resultat de tool envoye, Claude va repondre) |
| Aucun message recent (> seuil) | - | - | Idle |
Important : le stop_reason est dans message.stop_reason, pas au top-level.
Pattern 5: Matching PID -> JSONL (REVISE par la recherche)
What: Trouver le bon JSONL pour un PID donne. Algorithme revise :
- Lire le cwd du PID via
readlink /proc/PID/cwd - Encoder le cwd pour obtenir le nom du dossier dans
~/.claude/projects/ - Lister les fichiers
.jsonldans ce dossier (exclure le sous-dossiersubagents/) - Prendre le fichier JSONL le plus recemment modifie (= session active)
- NE PAS dependre de
sessions-index.json(absent dans 85% des projets recents)
Anti-Patterns to Avoid
- Charger un JSONL entier en memoire : Les fichiers peuvent depasser 100 Mo. Toujours tail-read.
- Dependre de sessions-index.json : Obsolete depuis ~fev 2026. Seulement 13/91 projets en ont un.
- Utiliser
ps auxvia exec : Lire/procdirectement est plus fiable et ne depend pas du format de sortie deps. - Ignorer les subagents : Le dossier
<session-uuid>/subagents/contient des JSONL de sous-agents. Ne pas les confondre avec les sessions principales.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| Parsing des args CLI | Framework CLI (cobra, urfave) | flag stdlib |
Une seule commande, pas besoin de framework |
| Couleurs terminal | Lib de couleurs (fatih/color) | Codes ANSI directs | 5 couleurs max, pas besoin d'une dependance |
| Lecture /proc | Lib procfs | os.ReadFile + os.Readlink |
3 fichiers a lire par PID, trivial |
Insight : Phase 1 est volontairement zero-dependency. La complexite est dans la logique metier (heuristique d'etat, tail-read), pas dans les outils.
Common Pitfalls
Pitfall 1: sessions-index.json absent
What goes wrong: Le code essaie de lire sessions-index.json et echoue silencieusement, pas de metadonnees. Why it happens: Claude Code ne genere plus ce fichier depuis ~fevrier 2026. How to avoid: Lire les metadonnees directement depuis les entrees JSONL (chaque entree contient sessionId, cwd, gitBranch, version). Warning signs: 85% des projets recents n'ont pas de sessions-index.json.
Pitfall 2: JSONL > 100 Mo charge en memoire
What goes wrong: OOM ou lenteur extreme pour une commande one-shot.
Why it happens: Les sessions longues generent des JSONL volumineux (max observe: 23 Mo pour un subagent).
How to avoid: Tail-read : Seek(0, io.SeekEnd) puis lire en arriere par blocs de 8-16 Ko.
Warning signs: Temps de reponse > 1s pour vmux list.
Pitfall 3: Race condition sur le JSONL
What goes wrong: Le JSONL est en cours d'ecriture par Claude Code pendant la lecture.
Why it happens: Fichier append-only, ecriture concurrente.
How to avoid: Lire la derniere ligne complete (terminee par \n). Ignorer une derniere ligne tronquee.
Warning signs: Erreur JSON parse sur la derniere ligne.
Pitfall 4: Processus Claude != session active
What goes wrong: Un PID Claude existe mais aucun JSONL recent ne correspond.
Why it happens: Claude Code peut etre en phase de demarrage, ou le dossier projet n'existe pas encore.
How to avoid: Afficher le processus avec un etat "Unknown" si aucun JSONL n'est trouve.
Warning signs: PID sans dossier encode correspondant dans ~/.claude/projects/.
Pitfall 5: Confusion subagent / session principale
What goes wrong: Des JSONL de subagents sont affiches comme des sessions separees.
Why it happens: Structure <uuid>/subagents/agent-*.jsonl dans le dossier projet.
How to avoid: Ne lister que les *.jsonl directement dans le dossier projet, pas dans les sous-dossiers.
Warning signs: Sessions fantomes qui apparaissent et disparaissent.
Pitfall 6: Double-dash dans l'encoding des chemins avec des points
What goes wrong: /home/user/.config s'encode en -home-user--config (double dash). Le code d'encoding rate la correspondance.
Why it happens: Les . sont remplaces par -, ce qui cree un -- quand un dossier commence par ..
How to avoid: L'encoding est simple : remplacer / ET . par -. Verifie sur la machine.
Code Examples
Lecture de /proc/PID/cmdline
// Source: verification directe sur /proc/13736/cmdline
func readCmdline(pid int) ([]string, error) {
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
if err != nil {
return nil, err
}
// cmdline est separe par des null bytes
parts := strings.Split(strings.TrimRight(string(data), "\x00"), "\x00")
return parts, nil
}
Structure d'un message JSONL assistant
{
"parentUuid": "...",
"isSidechain": false,
"message": {
"role": "assistant",
"content": [
{"type": "text", "text": "..."},
{"type": "tool_use", "name": "Read", "id": "toolu_..."}
],
"stop_reason": "tool_use"
},
"type": "assistant",
"uuid": "...",
"timestamp": "2026-03-23T11:38:18.236Z",
"cwd": "/home/pierre/Code/vibe/vmux",
"sessionId": "3a0e1bbb-...",
"version": "2.1.81",
"gitBranch": "HEAD"
}
Structure d'un message progress (subagent)
{
"type": "progress",
"data": {"type": "agent_progress"},
"timestamp": "2026-03-23T12:00:43.006Z",
"sessionId": "...",
"cwd": "..."
}
Structure d'un message progress (hook)
{
"type": "progress",
"data": {"type": "hook_progress", "hookEvent": "...", "hookName": "..."},
"timestamp": "..."
}
Structure d'un message system (fin de tour)
{
"type": "system",
"subtype": "turn_duration",
"durationMs": 82049,
"timestamp": "...",
"sessionId": "...",
"cwd": "...",
"gitBranch": "HEAD"
}
State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|---|---|---|---|
sessions-index.json pour les metadonnees |
Metadonnees dans chaque entree JSONL | ~fev 2026 | Ne plus dependre de sessions-index.json |
| Claude Code v2.0.x | Claude Code v2.1.81 | mars 2026 | Champ slug ajoute, agent_progress pour les subagents |
Validation Architecture
Test Framework
| Property | Value |
|---|---|
| Framework | Go testing (stdlib) |
| Config file | Aucun (convention Go par defaut) |
| Quick run command | go test ./... |
| Full suite command | go test -v -race ./... |
Phase Requirements -> Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|---|
| DISC-01 | Detecter les processus Claude actifs | unit | go test -run TestFindClaudeProcesses -v |
Wave 0 |
| DISC-02 | Identifier cwd et worktree git | unit | go test -run TestProcessCwd -v |
Wave 0 |
| DISC-03 | Afficher la branche git | unit | go test -run TestGitBranch -v |
Wave 0 |
| STATE-01 | Detecter l'etat (Working/NeedsInput/Idle) | unit | go test -run TestDetectState -v |
Wave 0 |
| STATE-02 | Afficher un apercu de la sortie | unit | go test -run TestExtractPreview -v |
Wave 0 |
Sampling Rate
- Per task commit:
go test ./... - Per wave merge:
go test -v -race ./... - Phase gate: Full suite green before
/gsd:verify-work
Wave 0 Gaps
proc_test.go-- couvre DISC-01, DISC-02 (testable avec des fixtures /proc simulees)session_test.go-- couvre DISC-03, STATE-02 (fixtures JSONL)state_test.go-- couvre STATE-01 (fixtures JSONL avec differents patterns)display_test.go-- couvre le formatage de sortiego.mod+shell.nix-- infrastructure de base
Strategy TDD
Les tests pour /proc et JSONL doivent utiliser des fixtures (fichiers temporaires) plutot que de lire le vrai /proc. Cela permet :
- Executer les tests en CI sans processus Claude actifs
- Tester les cas limites (JSONL corrompu, processus zombie, etc.)
- Reproductibilite totale
Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|---|---|---|---|---|
| Go | Build & run | oui | 1.25.7 (via nix-shell) | - |
| /proc filesystem | Process detection | oui | procfs Linux 6.19 | - |
| git | Branch detection | oui | standard NixOS | Fallback sur gitBranch du JSONL |
Missing dependencies with no fallback: Aucune.
Open Questions
-
Seuil Idle
- Ce qu'on sait : D-05 mentionne "Derniere activite > seuil -> Idle" sans definir le seuil.
- Ce qui est flou : 30s ? 60s ? 5min ?
- Recommandation : Commencer a 60s. Configurable plus tard. En Phase 1, un const suffit.
-
Sessions-index.json obsolete vs D-02
- Ce qu'on sait : D-02 mentionne sessions-index.json comme source. La recherche montre qu'il est absent dans 85% des cas recents.
- Ce qui est flou : Faut-il quand meme le lire quand il existe (pour le champ
summary) ? - Recommandation : Lire sessions-index.json en bonus si present (pour
summary), mais ne JAMAIS en dependre. Le JSONL est la source primaire.
-
Multiple sessions par cwd
- Ce qu'on sait : Pas observe sur la machine actuelle (6 PID, 6 cwd differents).
- Ce qui est flou : Est-ce possible d'avoir 2 processus Claude dans le meme repertoire ?
- Recommandation : Prendre le JSONL le plus recemment modifie. Si 2 PID partagent le meme cwd, afficher 2 sessions separees pointant vers les memes JSONL (cas rare, accepter l'imprecision en v1).
Sources
Primary (HIGH confidence)
- Inspection directe de
/proc/*/cmdline,/proc/*/cwdsur la machine (6 processus Claude actifs) - Lecture directe de
~/.claude/projects/: 91 dossiers projets, 13 avec sessions-index.json - Analyse de fichiers JSONL reels (format des messages assistant, user, progress, system)
- Structure verifiee :
<uuid>/subagents/agent-*.jsonlpour les sous-agents
Secondary (MEDIUM confidence)
- Stack research (CLAUDE.md) : Go 1.25.7 verifie, Bubble Tea v2 pour phases futures
Tertiary (LOW confidence)
- Encoding exact des chemins (verifie sur ~10 exemples, peut-etre des cas speciaux non couverts avec des caracteres Unicode ou des espaces)
Metadata
Confidence breakdown:
- Standard stack: HIGH - Go stdlib uniquement, verifie sur machine
- Architecture: HIGH - Format JSONL et /proc verifies par inspection directe
- Pitfalls: HIGH - sessions-index.json obsolete decouvert par inspection reelle (13/91)
Research date: 2026-03-23 Valid until: 2026-04-23 (format JSONL stable, /proc stable)