Cómo osFoundry encuentra tus sesiones de Claude Code y Codex en disco, recreado en Python
~/.claude/projects/-Users-alex-code-shop-api/c5e1a7b2-9d40-4f3e-8b16-2a7c0e9d4f58.jsonl ~/.claude/projects/-Users-alex-code-shop-api/c5e1a7b2-9d40-4f3e-8b16-2a7c0e9d4f58/subagents/agent-.jsonl ~/.codex/state_5.sqlite -> table threads (id, rollout_path, cwd, git_branch, title, …) ~/.codex/sessions/2026/11/04/rollout-2026-11-04T10-00-00-.jsonl $ claude --resume c5e1a7b2-9d40-4f3e-8b16-2a7c0e9d4f58 $ codex resume 019a2f1c-7d0e-7b31-9c4e-5a1f0e2d8b63 Ese es todo el contrato entre las CLI y un selector de sesiones. Claude Code y Codex guardan cada uno sus sesiones en archivos normales dentro de tu carpeta personal, y cada uno acepta un id para reanudar una. La pantalla External agents de la app de escritorio de osFoundry es un lector montado sobre esos archivos (Rust, runtime/session_inventory.rs y runtime/external_sessions.rs). Lista sesiones de doce apps, busca por título, carpeta, rama, app e id, y lanza en una terminal el comando de reanudación correcto. A continuación tienes la parte de Claude Code y Codex, recreada como un script de Python de 150 líneas y ejecutada contra las carpetas de sesiones reales del Mac donde se escribió este artículo. En ningún punto del artículo se imprime contenido de las conversaciones. Cada número es un recuento, un tamaño o un tiempo. ¿Cómo se ve una sesión de Claude Code en disco? Un archivo JSONL por sesión, con el id de la sesión como nombre, dentro de una carpeta que lleva el nombre del directorio de trabajo. El nombre de la carpeta es la ruta absoluta con cada carácter que no sea letra o dígito sustituido por -: re.sub(r"[^A-Za-z0-9]", "-", cwd) # /Users/alex/code/shop-api -> -Users-alex-code-shop-api Comprobado con las 40 transcripciones de esta máquina: 40 de 40 carpetas siguen esa regla. La regla pierde información (my.app y my-app chocan, igual que / y .), así que nunca decodifiques el nombre de la carpeta. Lee cwd de los registros. Los subagentes (Explore y compañía, de la herramienta Task) escriben sus propias transcripciones en /subagents/agent-.jsonl. Aquí había 129. Nadie las reanuda como sesión, y osFoundry se salta cualquier archivo cuyo nombre base empiece por agent-. Cada línea es un registro con un type. Contando los tipos en las primeras 400 líneas de cada uno de los 40 archivos: type registros claves que importan para un índice assistant 4,060 requestedModel, effort, gitBranch, message.model attachment 3,197 cwd, gitBranch user 2,469 cwd, gitBranch, permissionMode, isSidechain, message.content, entrypoint custom-title 922 customTitle agent-name 827 agentName system, mode, queue-operation, file-history-, api-request, … 2,000+ no hacen falta Los registros de conversación (user, assistant, attachment, system) llevan todos sessionId, cwd, gitBranch, timestamp y version. message.content es o un string o una lista de bloques; en esas líneas los tipos de bloque fueron tool_result 2,261, tool_use 2,223, thinking 1,224 y text 754. El título viene de un registro custom-title o, en las versiones que lo escriben, de un registro ai-title con aiTitle. Aquí todos los archivos tenían custom-title y ninguno ai-title, así que lee los dos. ¿Por qué no parsear cada archivo entero? Porque son grandes. Las 40 transcripciones de primer nivel suman aquí 19.4 GB. La mediana es de 168 MB, el más grande pesa 2.07 GB y el percentil 90 está en 1.94 GB. Pasar el archivo mediano por json.loads línea a línea tardó 0.61 s (11,558 líneas, unos 279 MB/s en un Mac con Apple Silicon, con la caché de páginas caliente). A ese ritmo, parsear los 40 completos llevaría unos 70 s, antes de haber mostrado nada. Los datos que necesita una lista están en los dos extremos del archivo. Medido en los 40: el primer registro con cwd estaba como muy tarde en la línea 2 (mediana 2) el primer gitBranch y el primer registro user: como muy tarde en la línea 2 había un registro de título dentro de los últimos 512 KiB en 40 de 40 archivos ninguna de las primeras 80 líneas pasaba de 256 KiB Así que el índice lee las primeras 80 líneas y los últimos 512 KiB. Es lo mismo que hace osFoundry: HEAD_LINES = 80 con un límite de 256 KiB por línea en session_inventory.rs, y SETTINGS_TAIL = 512 * 1024 en external_sessions.rs, que se lee para sacar el último modelo, effort, modo de aprobación y rama. Leer cabeza y cola de los 40 archivos tardó 86 ms. HEAD_LINES, LINE_CAP, TAIL_BYTES = 80, 256 * 1024, 512 * 1024 def head(p, n=HEAD_LINES): out = [] with open(p, "rb") as f: for _ in range(n): line = f.readline(LINE_CAP + 1) if not line: break if len(line) > LINE_CAP: # a pasted file or image: skip the rest of it while line and not line.endswith(b"\n"): line = f.readline(1 nbytes else lines # first piece is a cut line El readline con límite es importante. Un turno de usuario con una imagen pegada es una sola línea de varios megas, y un simple for line in f la cargaría entera en memoria solo para tirarla. ¿Qué campos necesita de verdad un selector? Cinco, más una fecha: app, id, carpeta, rama y título. La cabeza da cwd, el primer gitBranch y un título de reserva sacado del primer mensaje real del usuario (que no sea sidechain ni empiece por
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to