aboutsummaryrefslogtreecommitdiffstats
path: root/cli/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-13 12:52:55 +0200
committeruser <user@clank>2026-08-13 12:52:55 +0200
commitf5712cda26448eff37f00cd3e25852ef37a85eee (patch)
tree92d9597f62821a0008b136224294948dcae73f92 /cli/src
parentdocs: record the flakiness as fixed, with the measurements (diff)
downloadsl0ppi-f5712cda26448eff37f00cd3e25852ef37a85eee.tar.gz
sl0ppi-f5712cda26448eff37f00cd3e25852ef37a85eee.tar.xz
sl0ppi-f5712cda26448eff37f00cd3e25852ef37a85eee.zip
cli: create sessions by attaching, to dodge the phantom-client bug
Reported symptom: a pane titled "MY FOCUS AND:" in a fresh sl0ppi session. That is zellij's multi-user indicator. A session created with `attach --create-background` keeps one client in its connected set forever, so `connected_clients.len() > 1` and every pane frame renders its multi-user form with one anonymous colour block. sl0ppi created every session that way, so every sl0ppi session looked like someone else was in it. Isolated by comparison rather than by reading: created with attach --create-background marker on the FIRST attach, and every one after created by an ordinary attach that dies no marker, ever Ruled out by measurement: not the CLI-action clients (15 actions with no attach leaves nothing behind), and not the creating client exiting too early (making it wait for a server ack -- 94ms, server answered -- changed nothing; that experiment was reverted rather than shipped with a comment claiming a fix it did not deliver). The client-id change did not cause this either: it only made it visible, because recycled ids used to hand the phantom's id straight to the next client that attached. So: when `sl0ppi` has a terminal it now creates the session by ATTACHING to it (`--layout X attach --create NAME`) instead of creating it in the background and attaching second. That is the path with no leak, and it is one process instead of two. Headless `up` keeps the background path, where no one is looking at a pane frame. This is a workaround. The fork bug is recorded in docs/roadmap.md with what has been ruled out and where to instrument next, and it still affects anyone calling `zellij attach -b` directly. Verified on the test box under a real pty: no marker on creation or on any later attach, both project tabs present, agents still staged (purpose='agent:main' held=true cmd='pi').
Diffstat (limited to 'cli/src')
-rw-r--r--cli/src/commands/workspace.ts31
-rw-r--r--cli/src/index.ts23
2 files changed, 47 insertions, 7 deletions
diff --git a/cli/src/commands/workspace.ts b/cli/src/commands/workspace.ts
index bc5d7d2..4eec946 100644
--- a/cli/src/commands/workspace.ts
+++ b/cli/src/commands/workspace.ts
@@ -27,6 +27,11 @@ export interface Context {
dryRun: boolean;
/** Discard a dead session of the same name instead of resurrecting it. */
fresh?: boolean;
+ /**
+ * Do not create the session here; return the layout so the caller can create
+ * it BY ATTACHING to it. See `UpResult.createWith`.
+ */
+ deferCreate?: boolean;
log: (message: string) => void;
}
@@ -94,7 +99,24 @@ function computePlan(ctx: Context, tabs: TabInfo[]): Plan {
* layout (which is also the only way tab purposes survived before the fork fix),
* and an existing one gets tabs added individually.
*/
-export function up(ctx: Context): { plan: Plan; created: boolean } {
+export interface UpResult {
+ plan: Plan;
+ created: boolean;
+ /**
+ * Set when the session still needs creating and the caller should do it by
+ * attaching: `sl0ppij --layout <createWith> attach --create <session>`.
+ *
+ * Creating with `attach --create-background` leaves a phantom client in the
+ * session for its whole life -- measured: a background-created session shows
+ * "MY FOCUS AND:" on its very first attach, one created by an ordinary attach
+ * never does. Deferring to the attach we were about to do anyway avoids it
+ * entirely; the background path stays for headless use, where nobody is
+ * looking at a pane frame.
+ */
+ createWith?: string;
+}
+
+export function up(ctx: Context): UpResult {
const { root, session } = ctx.resolved;
const client = ctx.client.forSession(session);
@@ -148,6 +170,13 @@ export function up(ctx: Context): { plan: Plan; created: boolean } {
const dir = mkdtempSync(join(tmpdir(), "sl0ppi-layout-"));
const layoutPath = join(dir, "session.kdl");
writeFileSync(layoutPath, layout);
+
+ if (ctx.deferCreate) {
+ rememberRoot(session, root);
+ ctx.log(`creating session ${session} (${specs.length} project tab(s))`);
+ return { plan: emptyPlan(), created: true, createWith: layoutPath };
+ }
+
client.createBackground(session, layoutPath);
rememberRoot(session, root);
diff --git a/cli/src/index.ts b/cli/src/index.ts
index 7211a08..4ae6810 100644
--- a/cli/src/index.ts
+++ b/cli/src/index.ts
@@ -221,22 +221,33 @@ export function main(argv: string[] = process.argv.slice(2)): void {
return;
}
default: {
- up(ctx);
+ // Attaching needs a terminal. Without this guard the client panics in
+ // os_input_output when stdin is a pipe, which is what happens whenever
+ // `sl0ppi up` runs from a script or a CI step.
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
+ const willAttach = interactive && !args.dryRun && sessionEnv !== ctx.resolved.session;
+
+ // When we are going to attach anyway, create the session BY attaching:
+ // background creation leaves a phantom client behind for the life of the
+ // session (see UpResult.createWith).
+ const result = up({ ...ctx, deferCreate: willAttach });
+
// Inside the session already? Focusing beats nesting a second client.
if (sessionEnv === ctx.resolved.session) {
if (ctx.resolved.project) open(ctx, ctx.resolved.project);
return;
}
if (args.dryRun) return;
- // Attaching needs a terminal. Without this guard the client panics in
- // os_input_output when stdin is a pipe, which is what happens whenever
- // `sl0ppi up` runs from a script or a CI step.
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
+ if (!willAttach) {
process.stdout.write(`session ${ctx.resolved.session} ready (not a tty; not attaching)\n`);
return;
}
+
const bin = process.env.SL0PPI_BIN ?? DEFAULT_BIN;
- const proc = spawnSync(bin, ["attach", ctx.resolved.session], { stdio: "inherit" });
+ const attachArgs = result.createWith
+ ? ["--layout", result.createWith, "attach", "--create", ctx.resolved.session]
+ : ["attach", ctx.resolved.session];
+ const proc = spawnSync(bin, attachArgs, { stdio: "inherit" });
process.exit(proc.status ?? 0);
}
}