---
name: list_claude_fleet
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func ListClaudeFleetFrom(claudeDir string) ([]ClaudeFleet, error) | func ListClaudeFleet() ([]ClaudeFleet, error)"
description: "Lista la flota de procesos Claude Code de la maquina local (Linux). Escanea ~/.claude/sessions/*.json, cruza cada PID vivo contra /proc para validar liveness (anti-PID-reciclado via procStart == campo 22 de /proc/<pid>/stat), une el goal/phase de ~/.claude/goals/<sessionId>.json, extrae KITTY_PID del environ y deriva los campos de display (Target, Rename). Devuelve todas las sesiones ordenadas por status (idle, waiting, busy, otro) y por updatedAt desc; el caller filtra por Alive. Pieza de datos de la app TUI fleetview."
tags: [claude-fleet, infra, claude, session, proc, fleet, tui, orchestration]
uses_functions: [resolve_pane_ids_go_infra]
uses_types: [claude_fleet_go_infra]
returns: [claude_fleet_go_infra]
returns_optional: false
error_type: "error_go_core"
imports: []
params:
  - name: "claudeDir"
    desc: "Directorio raiz de Claude Code a escanear (ej. /home/enmanuel/.claude). ListClaudeFleetFrom lo recibe explicito (testeable con t.TempDir()); ListClaudeFleet lo resuelve via os.UserHomeDir() + .claude."
output: "Slice de ClaudeFleet (claude_fleet_go_infra), una entrada por sesion con JSON parseable en sessions/. Cada entrada lleva PID, KittyPID, SessionID, Rename, Target, Goal, Phase, Status, Cwd, TmuxWindow (\"\"), PaneID, Alive y UpdatedAt. ListClaudeFleet() puebla PaneID (\"%N\", identificador estable del pane) cruzando cada PID vivo con los panes del socket $FLEET_SOCKET (default \"fleet\") via resolve_pane_ids_go_infra; ListClaudeFleetFrom() deja PaneID \"\" (no hace tmux). Ordenado por rango de status y luego por UpdatedAt descendente. Devuelve slice vacio (sin error) si la carpeta sessions/ no existe; error si no se puede leer la carpeta por otra causa."
tested: true
tests: ["TestListClaudeFleetFrom", "TestListClaudeFleetFromMissingDir"]
test_file_path: "functions/infra/list_claude_fleet_test.go"
file_path: "functions/infra/list_claude_fleet.go"
notes: "Misma fuente de verdad que reboot_all_claudes_bash_infra (~/.claude/sessions/<PID>.json de Claude Code 2.1.x: pid, sessionId, cwd, procStart, status, updatedAt). Solo LEE y valida — no relanza ni mata nada. La validacion anti-PID-reciclado replica la del bash (procStart del JSON vs campo 22 de /proc/<pid>/stat) pero parseando de forma robusta el comm (campo 2 entre parentesis, que puede contener espacios y ')'): se toma lo que hay tras el ULTIMO ')' y starttime es el indice 19 de ese resto. TmuxWindow queda \"\" (se rellena en una fase posterior). Build tag //go:build !windows (depende de /proc, no portable a Windows)."
---

## Ejemplo

```go
package main

import (
	"fmt"

	"fn-registry/functions/infra"
)

func main() {
	fleet, err := infra.ListClaudeFleet() // escanea ~/.claude
	if err != nil {
		panic(err)
	}
	for _, c := range fleet {
		if !c.Alive {
			continue // el caller filtra las sesiones muertas
		}
		fmt.Printf("[%s] %-20s pid=%d kitty=%d  %s\n",
			c.Status, c.Rename, c.PID, c.KittyPID, c.Target)
	}
}
```

```go
// Variante testeable: escanea un directorio arbitrario (fixtures en tests).
fleet, _ := infra.ListClaudeFleetFrom("/home/enmanuel/.claude")
fmt.Println(len(fleet), "sesiones conocidas")
```

## Cuando usarla

Cuando necesites enumerar las sesiones de Claude Code vivas en la maquina local para mostrarlas, monitorizarlas o actuar sobre ellas (TUI fleetview, dashboards, automatizaciones). Da el join PID -> sessionId -> cwd -> goal/phase ya resuelto y validado contra /proc, en lugar de reimplementarlo a mano cada vez. Usa `ListClaudeFleetFrom` en tests (inyectando un directorio con fixtures) y `ListClaudeFleet` en runtime real.

## Gotchas

- **Impura: lee el filesystem y /proc.** No es determinista entre llamadas (las sesiones nacen y mueren). Solo lectura — nunca mata ni relanza procesos.
- **Anti-PID-reciclado.** `Alive` solo es true si el proceso existe Y su starttime (campo 22 de `/proc/<pid>/stat`) coincide con el `procStart` del JSON. Un JSON huerfano cuyo PID fue reasignado a otro proceso se marca `Alive=false` aunque ese PID este vivo. Si el JSON no trae `procStart`, basta con que el proceso exista.
- **Parseo del `comm` en /proc/<pid>/stat.** El campo 2 (comm) va entre parentesis y puede contener espacios y el caracter ')'. La funcion parsea tomando lo que hay tras el ULTIMO ')'; un split ingenuo por espacios daria un starttime equivocado.
- **/proc no es portable.** Build tag `//go:build !windows`; depende de `/proc/<pid>/stat` y `/proc/<pid>/environ` (Linux). En macOS/BSD no funciona tal cual.
- **environ ilegible -> KittyPID=0.** Si `/proc/<pid>/environ` no es legible (permisos, proceso de otro usuario, o el proceso ya murio entre el ReadDir y el ReadFile) `KittyPID` cae a 0 sin error. Tambien es 0 legitimamente cuando claude no corre bajo kitty (ej. tmux remoto).
- **Devuelve TODAS las sesiones con JSON parseable**, vivas o muertas. El caller decide filtrar por `Alive`. Archivos no-`.json` y JSON corrupto se ignoran silenciosamente.
- **TmuxWindow siempre "".** Esta funcion no resuelve el window_id (@N); lo rellena el consumidor (fleetview) cuando lo necesita para el focus.
- **PaneID lo puebla solo `ListClaudeFleet()`, no `ListClaudeFleetFrom()`.** La variante con directorio (usada en tests y en bucles de render calientes) no llama a tmux: deja `PaneID` "". La publica resuelve el pane_id ("%N") contra `$FLEET_SOCKET` (default "fleet") via `resolve_pane_ids_go_infra`. Si el socket no existe o tmux no responde, todos los `PaneID` quedan "" sin error. El pane_id es estable de por vida del pane (inmune a los swaps de window que mueve el focus), a diferencia de `TmuxWindow`.
