diff options
| author | user <user@clank> | 2026-08-13 12:52:55 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-13 12:52:55 +0200 |
| commit | f5712cda26448eff37f00cd3e25852ef37a85eee (patch) | |
| tree | 92d9597f62821a0008b136224294948dcae73f92 /cli/src | |
| parent | docs: record the flakiness as fixed, with the measurements (diff) | |
| download | sl0ppi-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.ts | 31 | ||||
| -rw-r--r-- | cli/src/index.ts | 23 |
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); } } |
