---
title: "The 63-Second Prompt That Reopened a Session We Had Just Closed"
canonical: https://dxdev.com/blog/graceful-session-close-zombie-prevention/
datePublished: 2026-09-15
---
The session was fresh at 4:31 PM. By 6:06 PM it was closed: server off the LAN, metered key out of the environment, and one line we'd been putting off finally verified: "closed sessions no longer zombie back to life from a trailing prompt."

## How a closed session came back

Every Claude Code session writes a manifest file in the archive, one YAML block per session, `status: active` or `status: closed`. A hook fires on every `UserPromptSubmit` and reads that manifest to render the footer and route `/next`, `/sync`, `/close`. The original code assumed a prompt only ever arrives while a session is alive. That assumption was wrong. A prompt can arrive after `/close` too, because closing a Claude Code session doesn't atomically kill the process that's still capturing your keystrokes. You type something into a pane you thought was dead, or a queued message lands a few seconds late. The hook sees a `session_id` that matches a manifest and a prompt event, and does the only thing it knows how to do with a prompt event. It flips `status` back to `active`, clears `ended`, and appends a new sub-session row. The session you closed five minutes ago is alive again, its footer rendering, its ticket state drifting from whatever you actually did next.

## The 60-second lockout

Our first fix was to have `/close` take away the hook's write access for a fixed window: the manifest could not be touched for 60 seconds after `ended` got set. We ran it against a real close and then walked away from the keyboard for a minute to grab coffee. The trailing prompt landed at 63 seconds. Zombie, right through the block.

Tightening or widening the window wouldn't have helped, because a fixed number just moves the coin flip. The lockout also broke the legitimate case. Someone closes at 5:58 and comes back at 6:02 to keep working. That is a resume, and a blanket lock can't tell it from a stray prompt.

## Classifying the first prompt instead

The fix that shipped asks a different question. Instead of how long to block writes, `cmd_prompt()` in the session hook checks the manifest's `ended` timestamp against now and decides what kind of prompt this is.

Under 10 minutes is the grace window. The very first prompt in it gets special treatment. The session stays `status: closed`, its `outcome` and `duration` untouched, and the hook stamps a `post_close_prompt` field onto the manifest instead of reopening it.

Past 10 minutes there is no such thing as a stray prompt anymore. This is a deliberate resume, so `status` flips to `active`, `ended` clears, and a fresh sub-session row gets appended. That is exactly the old zombie behavior, but at 25 minutes out nobody's finger just slipped, so it is now the correct behavior.

## Proving it against fake manifests

We wrote it as a script instead of trusting our read of the diff. We built a fake closed manifest with `last_heartbeat` 2 minutes before "now" and fired the prompt hook against it twice.

- First prompt: status stayed `closed`, duration held at `44m`, and `post_close_prompt` got set to `True`.
- Second prompt on the same dead session: status flipped to `active`, `ended` cleared, and a new sub-session row appeared.

Then the edge case that matters: a manifest closed 25 minutes ago getting a prompt. Status went straight to `active` on the very first prompt, with no grace, because 25 minutes past `ended` a prompt has no business pretending to be an accident.

The 10-minute number is longer than the round trip of closing the pane while a finger catches the enter key and the terminal is still capturing input. It is shorter than any plausible "we stepped away and we're back." What makes it hold is the mechanism: a classifier that looks at one timestamp and treats the first prompt in the window as noise and everything after it as intent.

## What the 63-second miss taught us

If a tool can reopen closed work when a late input arrives, check whether it can tell a stray input right after close from a deliberate return. A fixed 60-second lockout failed when a prompt landed at 63 seconds, and a classifier on one timestamp with a 10-minute window held. Test it with a real close and a late input, because reading the code would not have caught the 63-second prompt.
