> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
feat(qorpa): concesion host_pids — sin ella el «restaurar» de shuma devolvia un shell mudo
Pedido del operador: "por que aca reinicio y abren los tabs pero sin claude,
mientras que en mi laptop reinicio y tengo el restaurar".
La maquinaria de restaurar YA EXISTE y no habia que disenar nada: es AE1 de
COLA-SHUMA.md, y shuma-daemon/src/claude_sesion.rs, dado el pid del proceso de un
panel, encuentra el claude que corre debajo y devuelve la linea para reabrirlo EN
LA MISMA CHARLA (claude --resume <uuid>). El dato sale del registro vivo que deja
Claude Code en ~/.claude/sessions/<pid>.json.
Lo que fallaba era la jaula, por dos razones y la segunda es peor:
1. El pid es de otro namespace. Con --unshare-pid, adentro el claude es el pid 2,
y el archivo se llama 2.json con "pid":2. El daemon vive en el anfitrion,
donde el pid 2 es kthreadd, y claude_sesion.rs exige que el pid sea
descendiente del panel: no casa nunca. El propio modulo dice que en ese caso
"el puesto se guarda como el shell que es", que es exactamente el sintoma.
2. Y las jaulas SE PISAN el registro. El $HOME va bind-mounteado compartido y
TODAS ven su claude como pid 2, asi que todas escriben el MISMO 2.json.
Medido: los tres registros que habia eran de una sola jaula y ninguno de la
sesion viva (procStart 72597 contra 988468 del archivo).
Por eso no alcanzaba con tocar shuma: con los registros pisandose no hay nada que
cruzar, y cualquier arreglo del lado del daemon habria sido imposible de
certificar. La concesion es la pieza que lo desbloquea, y con ella el pid que el
registro guarda ya es el del anfitrion, asi que claude_sesion.rs funciona SIN
cambios.
`host_pids` arranca cerrada como todas las demas y se enciende por instancia. En
la del agente va encendida, con el porque escrito al lado. Es una concesion real
y esta dicho en su doc: un `ps` de adentro ve los procesos del host. No abre nada
mas -- fs, red y capacidades siguen igual.
Certificado en metal con su brazo de control: una jaula de la instancia del
agente reporta `pid-adentro=32721` (antes 2) y su `ps` ve shuma-daemon y
sshd-session; una instancia sin la concesion sigue con su arbol propio.
diff --git a/crates/takana-cli/src/qorpa.rs b/crates/takana-cli/src/qorpa.rs
index 65230406..0ae14708 100644
--- a/crates/takana-cli/src/qorpa.rs
+++ b/crates/takana-cli/src/qorpa.rs
@@ -816,6 +816,23 @@ struct Grants {
/// instancia recupera la familia `mount`/`setns` que la jaula deniega por defecto.
#[serde(default)]
nesting: bool,
+ /// Compartir el **árbol de procesos del host** en vez de armar uno propio (`--unshare-pid`).
+ ///
+ /// Apagado por defecto: un `ps` de adentro no tiene por qué ver la máquina entera. Se
+ /// enciende cuando algo de ADENTRO tiene que ser identificable desde AFUERA por su pid.
+ ///
+ /// El caso que lo trajo (2026-09-25): Claude Code deja un registro vivo en
+ /// `~/.claude/sessions/<pid>.json` para poder reanudar una conversación, y shuma lo usa para
+ /// que restaurar una pestaña devuelva **la charla** y no un shell pelado. Con el namespace
+ /// propio ese pid es el de adentro —**2**, en todas las jaulas a la vez— así que (a) el
+ /// daemon, que vive afuera, no puede cruzarlo contra `/proc`, y (b) como el `$HOME` está
+ /// compartido, **todas las jaulas escriben el mismo `2.json` y se pisan**. Medido: los tres
+ /// registros que había eran de la misma jaula y ninguno de la sesión viva.
+ ///
+ /// ⚠ Es una concesión real: con esto la instancia ve y puede señalizar los procesos del host
+ /// que su uid alcance. No abre nada más (fs, red y capacidades siguen igual).
+ #[serde(default)]
+ host_pids: bool,
/// Mapear tu uid a **root adentro**. Encendido por defecto: es lo que quieren `dnf`/`pacman`.
///
/// ⚠ MEDIDO 2026-09-03, y es un conflicto real con `nesting`: con `--uid 0`, un userns anidado
@@ -954,6 +971,7 @@ impl Default for Grants {
Grants {
sockets: Vec::new(), devices: Vec::new(), dirs: Vec::new(),
network: false, nesting: false, root: true, seal_image: false,
+ host_pids: false,
wayland_raw: false, wayland_globals: Vec::new(),
}
}
@@ -1590,11 +1608,16 @@ fn run_instance(
}
let mut args: Vec<String> = vec![
- "--unshare-pid", "--unshare-ipc", "--unshare-uts", "--unshare-cgroup-try",
+ "--unshare-ipc", "--unshare-uts", "--unshare-cgroup-try",
"--hostname", "qorpa",
"--clearenv",
"--die-with-parent",
].into_iter().map(String::from).collect();
+ // El árbol de procesos propio se arma salvo que la instancia pida compartir el del host
+ // (`host_pids`). Va al FRENTE para no cambiar el orden que tenían las banderas.
+ if !inst.grants.host_pids {
+ args.insert(0, "--unshare-pid".into());
+ }
if mapeado.is_none() {
// bwrap crea el suyo, con un solo id.
args.insert(0, "--unshare-user".into());
diff --git a/scripts/servidor/qorpa-claude.instance.toml b/scripts/servidor/qorpa-claude.instance.toml
index afb8af96..41b016fb 100644
--- a/scripts/servidor/qorpa-claude.instance.toml
+++ b/scripts/servidor/qorpa-claude.instance.toml
@@ -25,6 +25,17 @@ network = true
# `nesting`: `takana build` levanta su sandbox con bwrap ADENTRO. Sin esto, la jaula puede leer las
# recetas y no puede construir ninguna, que es justo la mitad del pedido.
nesting = true
+# `host_pids`: comparte el árbol de procesos del host en vez de armar uno propio.
+#
+# Hace falta para que «el restaurar» de shuma devuelva la CONVERSACIÓN y no un shell pelado.
+# Claude Code deja un registro vivo en `~/.claude/sessions/<pid>.json` y shuma lo cruza contra
+# /proc para reabrir la charla con `--resume <uuid>`. Con namespace propio ese pid es el de
+# adentro —**2**, en todas las jaulas a la vez— así que el daemon no puede cruzarlo, y como el
+# $HOME está compartido todas las jaulas escriben el MISMO `2.json` y se pisan (medido el
+# 2026-09-25: los tres registros eran de una sola jaula, ninguno de la sesión viva).
+#
+# ⚠ Es una concesión real: un `ps` de adentro ve los procesos del host. No abre nada más.
+host_pids = true
root = true
wayland_raw = false
seal_image = false