Files
vmux/.planning/phases/01-session-discovery/01-RESEARCH.md
2026-03-23 13:04:40 +01:00

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 /proc pour 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 le projectPath du 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-color pour 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

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 :

  1. Lire le cwd du PID via readlink /proc/PID/cwd
  2. Encoder le cwd pour obtenir le nom du dossier dans ~/.claude/projects/
  3. Lister les fichiers .jsonl dans ce dossier (exclure le sous-dossier subagents/)
  4. Prendre le fichier JSONL le plus recemment modifie (= session active)
  5. 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 aux via exec : Lire /proc directement est plus fiable et ne depend pas du format de sortie de ps.
  • 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 sortie
  • go.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

  1. 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.
  2. 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.
  3. 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/*/cwd sur 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-*.jsonl pour 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)