Close
Close a finished session, terminate an execution that must stop, and recover a turn with an unknown outcome
There are three ways to end a session. Close a session whose work is done. Terminate if you must stop it right now. If terminate leaves a turn with an unknown outcome, make a recovery decision. Continue using the session created in Start (sessionId in the SDK, SID in the HTTP API).
| close | terminate | recovery decision close | |
|---|---|---|---|
| SDK | sessions.close | sessions.terminate | sessions.recoveryDecisions.create |
| HTTP API | POST …/close | POST …/terminate | POST …/recovery-decisions |
| scope | sessions:control | sessions:control | sessions:recover |
| Turn in progress | Runs to the end and saves a checkpoint | Kills the execution. The turn may become outcome_unknown | Targets a session that has already stopped |
| Queued input | Cancelled when closed | Cancelled immediately | Cancelled immediately |
| Result | closed (cannot be undone) | stopped (can resume) | closed (cannot be undone) |
close
Close waits for the running turn to finish. Turns that have not started are cancelled (cancelled_turn_count). The example below closes after the first turn has finished.
import { closeResult, waitForReceipt, waitForTurn } from "@werft-ai/sdk";
await waitForTurn(werft, sessionId, created.turn_id);
const { revision } = await werft.sessions.get(sessionId);
const accepted = await werft.sessions.close(sessionId, {
expected_revision: revision,
reason: "work is done",
});
const receipt = await waitForReceipt(werft, accepted.receipt_id);
console.log(receipt.operation, receipt.status, closeResult(receipt));close succeeded {
checkpoint_revision: 0,
cancelled_turn_count: 0,
resulting_admission_state: 'closed'
}closeResult extracts the close receipt's result as a type. Use closeBlocked(session) to see whether an open request is blocking it.
While closing, the session is closing. An open permission request or question blocks close. Answer it or interrupt to unblock. After 60 seconds, attention becomes CLOSE_BLOCKED. After the session is closed, messages and pause, resume, close, and terminate get 409 SESSION_CLOSED. An interrupt that points at an already finished turn is still a successful no-op (no_op: true) after the session is closed.
terminate
Terminate takes the execution down immediately. It does not undo what a tool has already done outside. Terminating an idle session makes it stopped immediately, and you can resume it as is. If a turn was running, that turn becomes outcome_unknown and the session waits for a recovery decision.
const runaway = await werft.sessions.create({
profile_id: "claude-coding-local",
repository_id: "sample-app",
message:
'GATE-SPEC {"id":"runaway","steps":[],"final":"never","finalDelayMs":300000}',
});
const id = runaway.session_id;
while ((await werft.sessions.turns.get(id, runaway.turn_id)).status !== "running") {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
const current = await werft.sessions.get(id);
const terminated = await werft.sessions.terminate(id, {
expected_revision: current.revision,
reason: "runaway turn",
});
console.log(terminated.receipt_status, terminated.external_effects_reverted);
while ((await werft.sessions.get(id)).admission_state !== "recovery_required") {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
console.log((await werft.sessions.turns.get(id, runaway.turn_id)).status);accepted false
outcome_unknownRecovery decision
outcome_unknown is neither success nor failure. A person who has checked what the turn did outside decides.
| Decision | What it does |
|---|---|
abandon | Leaves the turn as cancelled and lets the session continue from the last checkpoint |
confirm_completed | When the checkpoint covers that turn, accepts it as finished. Needs evidence_ref |
start_fresh | Discards the earlier conversation and runs the queued input in a new Claude Code session |
retry_restore | After fixing the cause of the checkpoint restore failure, restores from the same checkpoint again |
close | Ends the session. This cannot be undone |
const stopped = await werft.sessions.get(id);
const decided = await werft.sessions.recoveryDecisions.create(id, {
decision: "close",
expected_revision: stopped.revision,
reason: "nothing to keep",
});
const decision = await waitForReceipt(werft, decided.receipt_id);
console.log(decision.operation, decision.result);recovery_decision {
resumable: false,
checkpoint_revision: null,
resulting_admission_state: 'closed'
}The key needs sessions:recover.
For abandon and confirm_completed, also send the target turn as target_turn_id. Other decisions do not accept it (400). If the abandon receipt's result.resumable is true, continue with resume.
Next steps
- receipt: read the result of a control command
- Sessions without a repository
session-close.tssample: an SDK script that runs session creation, waiting for the turn while receiving events, allowing a permission request, close, and the rejection of a message after close (409SESSION_CLOSED) in one go