$.session.compact() from a plugin module is refused in Claude Desktop sessions ("not available in a headless (-p / SDK) session yet")
Environment
- Claude Desktop 2.37337.1 (macOS), bundled Claude Code 2.1.295 (
required_versionin main.log) - Plugin module registered on
session.compactandturn.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:
- A
session.compacthook returns{ messages: [{ role: "user", text, toolUses: [] }] }without callingnext(). The transcript gets acompact_boundaryrow followed by exactly that message, no summarizer request, and CLAUDE.md re-attaches on the next prompt. Verified headlessly on 2.1.295. - 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.