Compacts the session once it is 20% full and has been idle 50 minutes, while the prompt cache is still warm.

Claude Code mods by Wrysk, in one marketplace.
| Mod | What it does |
|---|---|
idle-compact | Compacts a session that is at least 20% full once it has sat idle for 50 to 60 minutes, while the prompt cache is still warm. |
codex-lane | Runs worker jobs on Codex in the background, with completion notifications and a /codex pane. Needs the codex CLI. |
In any terminal session, at the prompt:
/plugin install idle-compact --marketplace wryskware/claude-mods
/plugin install codex-lane --marketplace wryskware/claude-mods
Answer y to add the marketplace, then choose the user scope so every session gets the mod. Its hooks run in that session from then on, with no reload.
Or from a shell:
claude plugin marketplace add wryskware/claude-mods
claude plugin install idle-compact@claude-mods --scope user
claude plugin install codex-lane@claude-mods --scope user
To pick up a new version, run claude plugin marketplace update claude-mods and claude plugin update <mod>@claude-mods, then /reload-plugins in the session.
To work on a mod from a clone, add the clone as the marketplace instead of the GitHub one: claude plugin marketplace add <path to the clone>. Edits then reach a session on /reload-plugins, with no new version.
claude plugin validate mods/idle-compact
claude plugin test mods/idle-compact
claude plugin validate mods/codex-lane
Each mod is one folder under mods/: .claude-plugin/plugin.json, hooks/hooks.json naming the hooks module, and the *.test.ts files beside it. The mod's .claude-plugin/types/ is written by the engine and is not tracked.
Anyone can install these from wryskware/claude-mods with the lines above. No clone is needed.
hooks/register.ts 74 lines1import type { EngineInterface, Register } from 'claude-code'
2
3// The prompt cache lapses an hour after the last request. A compaction reads
4// the whole context once, which is cheap only while that cache is still warm,
5// so it runs in the window from the idle mark until the cache lapses.
6const IDLE_MS = 50 * 60_000
7const CACHE_LAPSES_MS = 60 * 60_000
8const THRESHOLD_PERCENT = 20
9const TICK_MS = 60_000
10
11// Kept in the module, so a reload starts over. A fresh start counts as
12// activity, which errs toward waiting longer, never toward compacting early.
13let lastActivity: number | undefined
14let compactedFor: number | undefined
15let armed = false
16let busy = false
17
18const inWindow = (now: number, stamp: number) => {
19 const idle = now - stamp
20 return idle >= IDLE_MS && idle < CACHE_LAPSES_MS
21}
22
23const tick = async ($: EngineInterface) => {
24 if (busy || lastActivity === undefined) return
25 const stamp = lastActivity
26 if (compactedFor === stamp) return
27 busy = true
28 try {
29 if (!inWindow(await $.clock.now(), stamp)) return
30 const { context } = await $.session.usage()
31 if ((context.percent ?? 0) < THRESHOLD_PERCENT) return
32 // The usage read takes time: the window must still hold, and nothing may
33 // have run since, or the compaction is left alone.
34 if (lastActivity !== stamp || !inWindow(await $.clock.now(), stamp)) return
35 const result = await $.session.compact()
36 compactedFor = stamp
37 $.ui.log(`idle compact: ${result.skip ?? 'compacted'}`, { to: 'debug' })
38 } catch {
39 // Refused (a turn was running) or a failed read: the next tick tries
40 // again, for as long as the window holds.
41 } finally {
42 busy = false
43 }
44}
45
46const arm = ($: EngineInterface) => {
47 if (armed) return
48 armed = true
49 $.clock.every(TICK_MS, () => void tick($))
50}
51
52const touch = async ($: EngineInterface) => {
53 lastActivity = await $.clock.now()
54 arm($)
55}
56
57export const register: Register = on => {
58 on('session.start', async ($, e, next) => {
59 lastActivity ??= await $.clock.now()
60 arm($)
61 return next(e)
62 })
63
64 on('prompt.submit', async ($, e, next) => {
65 await touch($)
66 return next(e)
67 }).catch(($, e, next) => next(e))
68
69 on('turn.complete', async ($, e, next) => {
70 await touch($)
71 return next(e)
72 })
73}
74