$.session.compact() from a plugin module is refused in Claude Desktop sessions ("not available in a headless (-p / SDK) session yet")

Status Open
Reported on v2.1.295
Maintainer reply None cached
Activity 0 comments · opened Oct 15, 2026

Environment

  • Claude Desktop 2.37337.1 (macOS), bundled Claude Code 2.1.295 (required_version in main.log)
  • Plugin module registered on session.compact and turn.complete
  • Same plugin in an interactive terminal session on 2.1.299: the call is accepted

What we do

Our plugin runs a memory ceremony before compaction (it writes a first-person summary and recall entries to disk), then wants to replace the transcript with a one-line pointer instead of the engine's summary. The mechanism works end to end:

  1. A session.compact hook returns { messages: [{ role: "user", text, toolUses: [] }] } without calling next(). The transcript gets a compact_boundary row followed by exactly that message, no summarizer request, and CLAUDE.md re-attaches on the next prompt. Verified headlessly on 2.1.295.
  2. On turn.complete, when the ceremony has just finished, the module calls $.session.compact() so the person does not have to type /compact.

What happens in Desktop

Step 2 throws:

$.session.compact: not available in a headless (-p / SDK) session yet: compaction here runs inside a turn (a /compact prompt); catch it and carry on

Desktop Code-tab sessions go through the SDK path, so a plugin cannot start a compaction there even from turn.complete. The person has to type /compact, which the same hook answers correctly (trigger manual).

Ask

Allow $.session.compact() from turn.complete (or a later idle point) in SDK-driven interactive sessions such as Desktop, or expose an equivalent, e.g. $.command.run({ command: "compact" }) being accepted there. The message's "yet" suggests this is planned; this is a use case for it: a plugin that owns compaction needs to be able to start it once its pre-compaction work is on disk, otherwise the last step of an otherwise automatic flow is a typed command.

Repro

Minimal plugin: a module with

on("session.compact", async ($, e, next) => ({
  messages: [{ role: "user", text: "compacted by plugin", toolUses: [] }],
}));
on("turn.complete", async ($, e, next) => {
  try { await $.session.compact(); } catch (err) { $.ui.log(String(err)); }
  return next(e);
});

Send any prompt in a Desktop Code-tab session; the debug log shows the refusal above. Type /compact; the hook's message replaces the transcript as expected.

View original on GitHub ↗