Warns when the context window passes a threshold (80% by default) and suggests /save-context before compaction

A Claude Code mod that warns when the context window fills up and suggests saving the session's decisions with /save-context before automatic compaction summarises them away. Part of claude-plugins.
warn_percent setting, 50 to 95): a notice, a status line under the prompt (Context 83% · /save-context suggested) and /save-context suggested in the empty prompt box./save-context runs: the suggestions stop and the status line says so./context-alarm: shows how full the context is and the thresholds.It installs project-workflow from the same marketplace as a dependency, which provides /save-context.
/save-context ran, and when an automatic compaction starts./save-context suggested in the empty prompt box. It never runs save-context itself.skill.prompt (save-context only) notes that it ran and passes the skill's prompt on unchanged. session.compact only warns; the compaction still runs. command.run answers its own /context-alarm command and no other.Needs Claude Code 2.1.287 or later (mods). Tested on 2.1.289.
/plugin marketplace add mutlumehmet/claude-plugins
/plugin install context-alarm@mehmetmutlu
A mod runs inside Claude Code with your permissions. Read the code before you install any mod.
Made by Mehmet Mutlu, part of claude-plugins. More of his work at mehmetmutlu.dev.
hooks/register.js 103 lines1// context-alarm: watches how full the context window is. Past the threshold
2// (80% by default) it pins a status line, shows a toast and puts /save-context
3// in the empty prompt box as a dim suggestion (Tab takes it). Ten points later
4// it warns once more. Before an automatic compaction it warns if save-context
5// has not run since the first warning. It never runs save-context itself and
6// never stops a compaction.
7
8const REARM_BELOW = 10 // points under the threshold that count as "emptied again"
9
10let warnAt = 80
11let level = 0 // 0 quiet, 1 warned at warnAt, 2 warned at warnAt + 10
12let percent = null
13let saved = false // save-context ran since the first warning
14
15export function register(on, options) {
16 const n = Number.parseInt(String(options?.warn_percent ?? '80'), 10)
17 warnAt = Number.isFinite(n) && n >= 50 && n <= 95 ? n : 80
18
19 on('session.start', async ($, e, next) => {
20 await $.command.register({
21 name: 'context-alarm',
22 description: 'Show how full the context is and when context-alarm warns',
23 })
24 return next(e)
25 })
26
27 on('command.run', { command: 'context-alarm' }, async () => {
28 const now = percent === null ? 'not measured yet' : percent + '%'
29 return {
30 text:
31 'Context: ' + now + ' · warns at ' + warnAt + '% and ' + (warnAt + 10) + '%' +
32 ' · save-context since the warning: ' + (saved ? 'ran' : 'not run'),
33 }
34 })
35
36 // Every change in the fill. The main conversation's window only: subagents
37 // have their own and do not fire this.
38 on('session.measure', async ($, e, next) => {
39 if (e.changed.includes('context') && typeof e.context.percent === 'number') {
40 await onPercent($, e.context.percent)
41 }
42 return next(e)
43 })
44
45 // After the turn the prompt box is free, so the suggestion can show
46 on('turn.complete', async ($, e, next) => {
47 const result = await next(e)
48 if (!e.agentId && level > 0 && !saved) {
49 $.clock.after(300, () => suggest($))
50 }
51 return result
52 })
53
54 on('skill.prompt', { skill: 'save-context' }, async ($, e, next) => {
55 saved = true
56 if (percent !== null) $.ui.status(statusText())
57 return next(e)
58 })
59
60 on('session.compact', async ($, e, next) => {
61 if (!e.agentId && e.trigger === 'auto' && level > 0 && !saved) {
62 $.ui.toast('Auto compaction is starting and save-context has not run in this session. The summary may lose some details.', { timeoutMs: 12000 })
63 }
64 return next(e)
65 })
66}
67
68async function onPercent($, p) {
69 percent = p
70
71 if (p < warnAt - REARM_BELOW) {
72 // Emptied, usually by a compaction or /clear: start over
73 if (level > 0) $.ui.status(undefined)
74 level = 0
75 saved = false
76 return
77 }
78
79 if (p >= warnAt + 10 && level < 2) {
80 level = 2
81 $.ui.toast('Context ' + p + '% full. Auto compaction is close: run /save-context now.', { timeoutMs: 12000 })
82 } else if (p >= warnAt && level < 1) {
83 level = 1
84 $.ui.toast('Context ' + p + '% full. Time to save decisions with /save-context.', { timeoutMs: 10000 })
85 }
86
87 if (level > 0) $.ui.status(statusText())
88}
89
90function statusText() {
91 return saved
92 ? 'Context ' + percent + '% · save-context ran'
93 : 'Context ' + percent + '% · /save-context suggested'
94}
95
96async function suggest($) {
97 try {
98 await $.prompt.suggest({ text: '/save-context' })
99 } catch {
100 // Headless, or the box is busy: the status line still says it
101 }
102}
103