@rex_koh/subagent-budget-guard 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  "name": "subagent-budget-guard",
3
3
  "displayName": "Subagent Budget Guard",
4
4
  "description": "Hard-deny subagent launches, record verified subagent usage, and enforce a session budget against Claude Code's 5-hour rate-limit percentage.",
5
- "version": "0.1.2",
5
+ "version": "0.1.4",
6
6
  "author": {
7
7
  "name": "ClaudeSubAgentSuppressor"
8
8
  },
@@ -31,6 +31,15 @@
31
31
  "min": 0,
32
32
  "required": true
33
33
  },
34
+ "subagent_token_warning_threshold_percent": {
35
+ "type": "number",
36
+ "title": "Subagent token warning threshold percent",
37
+ "description": "Warn Claude to stop using subagents once verified subagent token usage reaches this percentage of max_subagent_tokens_per_session.",
38
+ "default": 95,
39
+ "min": 1,
40
+ "max": 100,
41
+ "required": true
42
+ },
34
43
  "session_five_hour_budget_percent": {
35
44
  "type": "number",
36
45
  "title": "Session 5-hour budget percent",
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Subagent Budget Guard
2
2
 
3
- Claude Code plugin that blocks subagents by default, records verified subagent usage, and enforces a session budget against Claude Code's 5-hour usage percentage.
3
+ Claude Code plugin that blocks subagents before setup, records verified subagent usage, and enforces a session budget against Claude Code's 5-hour usage percentage.
4
4
 
5
5
  ## Install
6
6
 
@@ -10,13 +10,14 @@ Recommended Claude Code install:
10
10
  /plugin marketplace add rexkoh425/ClaudeSubAgentSuppressor
11
11
  /plugin install subagent-budget-guard@subagent-budget-tools
12
12
  /reload-plugins
13
+ /subagent-budget-guard:setup
14
+ /reload-plugins
15
+ /subagent-budget-guard:verify
13
16
  ```
14
17
 
15
- Run after install:
18
+ Useful after install:
16
19
 
17
20
  ```text
18
- /subagent-budget-guard:setup
19
- /subagent-budget-guard:verify
20
21
  /subagent-budget-guard:report
21
22
  ```
22
23
 
@@ -43,4 +44,17 @@ Offline verification:
43
44
  node bin/verify.js --offline
44
45
  ```
45
46
 
46
- The plugin is strict by default: `max_concurrent_subagents` defaults to `0`, so normal subagent launches are blocked unless raised.
47
+ The plugin is strict before setup: `max_concurrent_subagents` defaults to `0`, so normal subagent launches are blocked unless raised. Run `/subagent-budget-guard:setup` to replace the long `--config ...` install command with the recommended config:
48
+
49
+ ```text
50
+ max_concurrent_subagents=1
51
+ max_subagent_tokens_per_session=100000
52
+ subagent_token_warning_threshold_percent=95
53
+ session_five_hour_budget_percent=25
54
+ absolute_five_hour_ceiling_percent=95
55
+ enforcement_enabled=true
56
+ ```
57
+
58
+ For existing installs, setup also removes obsolete `max_subagents_per_session` and `max_agent_team_tasks_per_session` options from this plugin's Claude settings.
59
+
60
+ `max_subagent_tokens_per_session` is enforced from verified `Agent.totalTokens` values after each completed subagent. `subagent_token_warning_threshold_percent` defaults to `95`; once verified subagent usage reaches that percentage, the plugin tells Claude to stop using subagents and blocks future subagent launches. Claude Code does not expose mid-run per-token subagent streaming to hooks, so a single running subagent can only be evaluated when it reports its final token total.
package/bin/setup.js CHANGED
@@ -11,6 +11,13 @@ async function main() {
11
11
  process.stdout.write(
12
12
  [
13
13
  'Subagent Budget Guard statusLine bridge installed.',
14
+ 'Recommended plugin config applied:',
15
+ ` max_concurrent_subagents=${result.pluginConfigOptions.max_concurrent_subagents}`,
16
+ ` max_subagent_tokens_per_session=${result.pluginConfigOptions.max_subagent_tokens_per_session}`,
17
+ ` subagent_token_warning_threshold_percent=${result.pluginConfigOptions.subagent_token_warning_threshold_percent}`,
18
+ ` session_five_hour_budget_percent=${result.pluginConfigOptions.session_five_hour_budget_percent}`,
19
+ ` absolute_five_hour_ceiling_percent=${result.pluginConfigOptions.absolute_five_hour_ceiling_percent}`,
20
+ ` enforcement_enabled=${result.pluginConfigOptions.enforcement_enabled}`,
14
21
  `Settings: ${result.settingsPath}`,
15
22
  `Bridge state: ${result.bridgePath}`,
16
23
  result.previousStatusLine
package/lib/guard.js CHANGED
@@ -13,17 +13,33 @@ import {
13
13
  } from 'node:fs/promises';
14
14
  import os from 'node:os';
15
15
  import path from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
+
16
20
  export const PLUGIN_NAME = 'subagent-budget-guard';
21
+ export const PLUGIN_ID = 'subagent-budget-guard@subagent-budget-tools';
17
22
 
18
23
  export const DEFAULT_CONFIG = Object.freeze({
19
24
  max_concurrent_subagents: 0,
20
25
  max_subagent_tokens_per_session: 0,
26
+ subagent_token_warning_threshold_percent: 95,
21
27
  session_five_hour_budget_percent: 25,
22
28
  absolute_five_hour_ceiling_percent: 95,
23
29
  enforcement_enabled: true
24
30
  });
25
31
 
32
+ export const SETUP_CONFIG = Object.freeze({
33
+ ...DEFAULT_CONFIG,
34
+ max_concurrent_subagents: 1,
35
+ max_subagent_tokens_per_session: 100000
36
+ });
37
+
26
38
  export const CONFIG_KEYS = Object.freeze(Object.keys(DEFAULT_CONFIG));
39
+ export const REMOVED_CONFIG_KEYS = Object.freeze([
40
+ 'max_subagents_per_session',
41
+ 'max_agent_team_tasks_per_session'
42
+ ]);
27
43
 
28
44
  const NUMBER_KEYS = new Set(
29
45
  CONFIG_KEYS.filter((key) => typeof DEFAULT_CONFIG[key] === 'number')
@@ -78,6 +94,10 @@ export function loadConfig(env = process.env) {
78
94
  100,
79
95
  config.absolute_five_hour_ceiling_percent
80
96
  );
97
+ config.subagent_token_warning_threshold_percent = Math.min(
98
+ 100,
99
+ Math.max(1, config.subagent_token_warning_threshold_percent)
100
+ );
81
101
 
82
102
  return config;
83
103
  }
@@ -87,7 +107,7 @@ export function getHomeDir(env = process.env) {
87
107
  }
88
108
 
89
109
  export function getPluginRoot(env = process.env) {
90
- return env.CLAUDE_PLUGIN_ROOT || path.resolve('.');
110
+ return env.CLAUDE_PLUGIN_ROOT || PACKAGE_ROOT;
91
111
  }
92
112
 
93
113
  export function getDataDir(env = process.env) {
@@ -125,6 +145,9 @@ function initialState(sessionId) {
125
145
  verifiedTokens: 0,
126
146
  totalDurationMs: 0,
127
147
  totalToolUseCount: 0,
148
+ tokenBudgetWarnings: 0,
149
+ tokenBudgetExceeded: false,
150
+ lastTokenBudgetNoticeAt: null,
128
151
  runs: []
129
152
  },
130
153
  agentTeam: {
@@ -191,7 +214,23 @@ async function acquireLock(sessionId, env, timeoutMs = 3000) {
191
214
  }
192
215
 
193
216
  async function readState(sessionId, env) {
194
- return readJson(stateFile(sessionId, env), initialState(sessionId));
217
+ return normalizeState(await readJson(stateFile(sessionId, env), initialState(sessionId)), sessionId);
218
+ }
219
+
220
+ function normalizeState(state, sessionId) {
221
+ const fresh = initialState(sessionId);
222
+ state.subagents = { ...fresh.subagents, ...(state.subagents || {}) };
223
+ state.agentTeam = { ...fresh.agentTeam, ...(state.agentTeam || {}) };
224
+ state.rateLimits = {
225
+ ...fresh.rateLimits,
226
+ ...(state.rateLimits || {}),
227
+ fiveHour: {
228
+ ...fresh.rateLimits.fiveHour,
229
+ ...(state.rateLimits?.fiveHour || {})
230
+ }
231
+ };
232
+ state.events = Array.isArray(state.events) ? state.events : [];
233
+ return state;
195
234
  }
196
235
 
197
236
  async function updateState(sessionId, env, updater) {
@@ -298,12 +337,62 @@ function fiveHourBudgetDecision(state, config) {
298
337
  return null;
299
338
  }
300
339
 
340
+ function formatCount(value) {
341
+ return Number(value || 0).toLocaleString('en-US');
342
+ }
343
+
344
+ function subagentTokenBudgetStatus(state, config) {
345
+ const limit = config.max_subagent_tokens_per_session;
346
+ if (!limit || limit <= 0) return null;
347
+
348
+ const used = Number(state.subagents.verifiedTokens || 0);
349
+ const percent = limit > 0 ? (used / limit) * 100 : 0;
350
+ const warningThreshold = config.subagent_token_warning_threshold_percent;
351
+
352
+ return {
353
+ used,
354
+ limit,
355
+ percent,
356
+ warningThreshold,
357
+ warningTokens: Math.ceil((limit * warningThreshold) / 100),
358
+ atWarning: percent >= warningThreshold,
359
+ atCap: used >= limit
360
+ };
361
+ }
362
+
363
+ function subagentTokenBudgetDecision(state, config, { includeWarning = true } = {}) {
364
+ if (!config.enforcement_enabled) return null;
365
+ const status = subagentTokenBudgetStatus(state, config);
366
+ if (!status) return null;
367
+
368
+ if (status.atCap) {
369
+ return {
370
+ severity: 'cap',
371
+ status,
372
+ reason: `Verified subagent token cap reached: ${formatCount(status.used)}/${formatCount(status.limit)} tokens (${status.percent.toFixed(1)}%). Stop using subagents and ask the user before continuing.`
373
+ };
374
+ }
375
+
376
+ if (includeWarning && status.atWarning) {
377
+ return {
378
+ severity: 'warning',
379
+ status,
380
+ reason: `Verified subagent token usage reached ${status.percent.toFixed(1)}% of the configured cap (${formatCount(status.used)}/${formatCount(status.limit)} tokens; warning threshold ${status.warningThreshold}%). Stop using subagents and ask the user before continuing.`
381
+ };
382
+ }
383
+
384
+ return null;
385
+ }
386
+
301
387
  function agentDenyReason(state, config) {
302
388
  if (!config.enforcement_enabled) return null;
303
389
 
304
390
  const budgetReason = fiveHourBudgetDecision(state, config);
305
391
  if (budgetReason) return budgetReason;
306
392
 
393
+ const tokenBudgetReason = subagentTokenBudgetDecision(state, config);
394
+ if (tokenBudgetReason) return tokenBudgetReason.reason;
395
+
307
396
  if (config.max_concurrent_subagents === 0) {
308
397
  return 'Subagent launch denied: max_concurrent_subagents is 0.';
309
398
  }
@@ -312,13 +401,6 @@ function agentDenyReason(state, config) {
312
401
  return `Subagent launch denied: max_concurrent_subagents ${config.max_concurrent_subagents} already reached.`;
313
402
  }
314
403
 
315
- if (
316
- config.max_subagent_tokens_per_session > 0 &&
317
- state.subagents.verifiedTokens >= config.max_subagent_tokens_per_session
318
- ) {
319
- return `Subagent launch denied: verified subagent tokens ${state.subagents.verifiedTokens} reached max_subagent_tokens_per_session ${config.max_subagent_tokens_per_session}.`;
320
- }
321
-
322
404
  return null;
323
405
  }
324
406
 
@@ -377,11 +459,13 @@ function usageTotal(usage = {}) {
377
459
 
378
460
  export async function handlePostToolUseAgent(input, env = process.env) {
379
461
  const sessionId = input?.session_id || 'unknown-session';
462
+ const config = loadConfig(env);
380
463
  const response = input?.tool_response || {};
381
464
  const status = response.status || 'unknown';
382
465
  const totalTokens =
383
466
  numberOrNull(response.totalTokens) ?? usageTotal(response.usage || {});
384
467
  const verified = status === 'completed' && totalTokens > 0;
468
+ let tokenBudgetNotice = null;
385
469
 
386
470
  await updateState(sessionId, env, (state) => {
387
471
  const run = {
@@ -409,6 +493,14 @@ export async function handlePostToolUseAgent(input, env = process.env) {
409
493
  state.subagents.verifiedTokens += totalTokens;
410
494
  state.subagents.totalDurationMs += run.totalDurationMs;
411
495
  state.subagents.totalToolUseCount += run.totalToolUseCount;
496
+ tokenBudgetNotice = subagentTokenBudgetDecision(state, config);
497
+ if (tokenBudgetNotice) {
498
+ state.subagents.tokenBudgetWarnings += 1;
499
+ state.subagents.lastTokenBudgetNoticeAt = nowIso();
500
+ if (tokenBudgetNotice.severity === 'cap') {
501
+ state.subagents.tokenBudgetExceeded = true;
502
+ }
503
+ }
412
504
  } else if (status === 'async_launched') {
413
505
  state.subagents.backgroundLaunched += 1;
414
506
  }
@@ -420,9 +512,22 @@ export async function handlePostToolUseAgent(input, env = process.env) {
420
512
  verified,
421
513
  totalTokens: run.totalTokens
422
514
  });
515
+ if (tokenBudgetNotice) {
516
+ pushEvent(state, {
517
+ type: 'subagent-token-budget-notice',
518
+ severity: tokenBudgetNotice.severity,
519
+ used: tokenBudgetNotice.status.used,
520
+ limit: tokenBudgetNotice.status.limit,
521
+ percent: tokenBudgetNotice.status.percent
522
+ });
523
+ }
423
524
  return state;
424
525
  });
425
526
 
527
+ if (tokenBudgetNotice) {
528
+ return { exitCode: 2, stdout: null, stderr: tokenBudgetNotice.reason };
529
+ }
530
+
426
531
  return { exitCode: 0, stdout: null, stderr: '' };
427
532
  }
428
533
 
@@ -530,7 +635,10 @@ export async function handleUserPromptSubmit(input, env = process.env) {
530
635
  const sessionId = input?.session_id || 'unknown-session';
531
636
  const config = loadConfig(env);
532
637
  const state = await readState(sessionId, env);
533
- const reason = fiveHourBudgetDecision(state, config);
638
+ const tokenBudgetReason = subagentTokenBudgetDecision(state, config, {
639
+ includeWarning: false
640
+ });
641
+ const reason = tokenBudgetReason?.reason || fiveHourBudgetDecision(state, config);
534
642
 
535
643
  if (!reason) {
536
644
  return { exitCode: 0, stdout: null, stderr: '' };
@@ -588,6 +696,7 @@ export async function buildReport(sessionId, env = process.env) {
588
696
  fiveHour.latestUsedPercentage !== null && fiveHour.baselineUsedPercentage !== null
589
697
  ? Math.max(0, fiveHour.latestUsedPercentage - fiveHour.baselineUsedPercentage)
590
698
  : null;
699
+ const tokenBudget = subagentTokenBudgetStatus(state, config);
591
700
 
592
701
  return {
593
702
  plugin: PLUGIN_NAME,
@@ -596,6 +705,9 @@ export async function buildReport(sessionId, env = process.env) {
596
705
  state,
597
706
  summary: {
598
707
  verifiedTokenLabel: `${state.subagents.verifiedTokens.toLocaleString('en-US')} verified tokens`,
708
+ subagentTokenBudget: tokenBudget
709
+ ? `${formatCount(tokenBudget.used)}/${formatCount(tokenBudget.limit)} verified tokens (${tokenBudget.percent.toFixed(1)}%)`
710
+ : 'no verified-token cap',
599
711
  activeSubagents: `${state.subagents.active}/${config.max_concurrent_subagents}`,
600
712
  fiveHourBudget:
601
713
  consumed === null
@@ -614,6 +726,7 @@ export function formatReport(report) {
614
726
  `Enforcement: ${config.enforcement_enabled ? 'enabled' : 'disabled'}`,
615
727
  `Subagents: allowed ${state.subagents.allowed}, denied ${state.subagents.denied}, active ${state.subagents.active}, lifecycle starts ${state.subagents.lifecycleStarted}, lifecycle stops ${state.subagents.lifecycleStopped}`,
616
728
  `Verified usage: ${summary.verifiedTokenLabel}, ${state.subagents.totalToolUseCount} subagent tool calls, ${state.subagents.totalDurationMs} ms`,
729
+ `Subagent token budget: ${summary.subagentTokenBudget}`,
617
730
  `Background launches: ${state.subagents.backgroundLaunched} lifecycle-counted, token totals pending`,
618
731
  `Agent-team tasks: created ${state.agentTeam.created}, denied ${state.agentTeam.denied}, completed ${state.agentTeam.completed}`,
619
732
  `5-hour budget: ${summary.fiveHourBudget}`
@@ -659,6 +772,42 @@ function isBridgeStatusLine(statusLine) {
659
772
  );
660
773
  }
661
774
 
775
+ function isPlainObject(value) {
776
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
777
+ }
778
+
779
+ function applySetupPluginConfig(
780
+ settings,
781
+ { pluginId = PLUGIN_ID, setupConfig = SETUP_CONFIG } = {}
782
+ ) {
783
+ if (!isPlainObject(settings.pluginConfigs)) {
784
+ settings.pluginConfigs = {};
785
+ }
786
+
787
+ const currentEntry = isPlainObject(settings.pluginConfigs[pluginId])
788
+ ? settings.pluginConfigs[pluginId]
789
+ : {};
790
+ const currentOptions = isPlainObject(currentEntry.options)
791
+ ? currentEntry.options
792
+ : {};
793
+ const nextOptions = { ...currentOptions };
794
+
795
+ for (const key of REMOVED_CONFIG_KEYS) {
796
+ delete nextOptions[key];
797
+ }
798
+
799
+ for (const key of CONFIG_KEYS) {
800
+ nextOptions[key] = setupConfig[key];
801
+ }
802
+
803
+ settings.pluginConfigs[pluginId] = {
804
+ ...currentEntry,
805
+ options: nextOptions
806
+ };
807
+
808
+ return nextOptions;
809
+ }
810
+
662
811
  export async function installStatusLineBridge({
663
812
  homeDir = getHomeDir(),
664
813
  pluginRoot = getPluginRoot(),
@@ -680,6 +829,7 @@ export async function installStatusLineBridge({
680
829
  padding: existing?.padding ?? previousStatusLine?.padding ?? 0,
681
830
  refreshInterval: existing?.refreshInterval ?? 5
682
831
  };
832
+ const pluginConfigOptions = applySetupPluginConfig(settings);
683
833
 
684
834
  settings.statusLine = nextStatusLine;
685
835
  await writeJsonAtomic(settingsPath, settings);
@@ -696,7 +846,9 @@ export async function installStatusLineBridge({
696
846
  settingsPath,
697
847
  bridgePath,
698
848
  command,
699
- previousStatusLine
849
+ previousStatusLine,
850
+ pluginConfigApplied: true,
851
+ pluginConfigOptions
700
852
  };
701
853
  }
702
854
 
@@ -751,8 +903,8 @@ export async function renderStatusLine(input, {
751
903
 
752
904
  const guardSegment =
753
905
  fiveHour.latestUsedPercentage === null
754
- ? `SBG agents ${report.state.subagents.active}/${report.config.max_concurrent_subagents} | 5h unknown`
755
- : `SBG agents ${report.state.subagents.active}/${report.config.max_concurrent_subagents} | 5h ${fiveHour.latestUsedPercentage.toFixed(1)}%`;
906
+ ? `SBG agents ${report.state.subagents.active}/${report.config.max_concurrent_subagents} | tokens ${report.summary.subagentTokenBudget} | 5h unknown`
907
+ : `SBG agents ${report.state.subagents.active}/${report.config.max_concurrent_subagents} | tokens ${report.summary.subagentTokenBudget} | 5h ${fiveHour.latestUsedPercentage.toFixed(1)}%`;
756
908
 
757
909
  return previous ? `${previous} | ${guardSegment}` : guardSegment;
758
910
  }
package/lib/verifier.js CHANGED
@@ -5,6 +5,9 @@ import path from 'node:path';
5
5
 
6
6
  import {
7
7
  CONFIG_KEYS,
8
+ PLUGIN_ID,
9
+ REMOVED_CONFIG_KEYS,
10
+ SETUP_CONFIG,
8
11
  buildReport,
9
12
  handlePostToolUseAgent,
10
13
  handlePreToolUseAgent,
@@ -87,7 +90,7 @@ export async function runOfflineVerification({
87
90
  entry.source?.package === '@rex_koh/subagent-budget-guard',
88
91
  'marketplace npm package mismatch'
89
92
  );
90
- assert(entry.source?.version === '0.1.2', 'marketplace npm version mismatch');
93
+ assert(entry.source?.version === '0.1.4', 'marketplace npm version mismatch');
91
94
  return marketplacePath;
92
95
  });
93
96
  } else {
@@ -287,6 +290,53 @@ export async function runOfflineVerification({
287
290
  }
288
291
  });
289
292
 
293
+ await withCheck(result, 'setup-applies-plugin-config', async () => {
294
+ const dataDir = await mkdtemp(path.join(os.tmpdir(), 'sbg-verify-data-'));
295
+ const homeDir = await mkdtemp(path.join(os.tmpdir(), 'sbg-verify-home-'));
296
+ try {
297
+ const { mkdir, writeFile } = await import('node:fs/promises');
298
+ const claudeDir = path.join(homeDir, '.claude');
299
+ const settingsPath = path.join(claudeDir, 'settings.json');
300
+ await mkdir(claudeDir, { recursive: true });
301
+ await writeFile(
302
+ settingsPath,
303
+ JSON.stringify({
304
+ pluginConfigs: {
305
+ [PLUGIN_ID]: {
306
+ options: {
307
+ max_subagents_per_session: 9,
308
+ max_concurrent_subagents: 0,
309
+ max_agent_team_tasks_per_session: 4,
310
+ max_subagent_tokens_per_session: 0,
311
+ enforcement_enabled: false
312
+ }
313
+ }
314
+ }
315
+ })
316
+ );
317
+
318
+ await installStatusLineBridge({
319
+ homeDir,
320
+ pluginRoot: root,
321
+ pluginData: dataDir
322
+ });
323
+
324
+ const settings = await readJson(settingsPath);
325
+ const options = settings.pluginConfigs?.[PLUGIN_ID]?.options;
326
+ assert(options, `missing pluginConfigs.${PLUGIN_ID}.options`);
327
+ for (const key of CONFIG_KEYS) {
328
+ assert(options[key] === SETUP_CONFIG[key], `setup config ${key} mismatch`);
329
+ }
330
+ for (const key of REMOVED_CONFIG_KEYS) {
331
+ assert(!(key in options), `obsolete option ${key} was not removed`);
332
+ }
333
+ return `${PLUGIN_ID} recommended setup config applied`;
334
+ } finally {
335
+ await rm(dataDir, { recursive: true, force: true });
336
+ await rm(homeDir, { recursive: true, force: true });
337
+ }
338
+ });
339
+
290
340
  result.ok = result.failures.length === 0;
291
341
  return result;
292
342
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rex_koh/subagent-budget-guard",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Claude Code plugin that blocks subagents by default, records verified subagent usage, and enforces 5-hour usage budgets.",
5
5
  "license": "MIT",
6
6
  "author": "ClaudeSubAgentSuppressor",
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Install or refresh the Subagent Budget Guard statusLine bridge so 5-hour rate-limit percentages can be captured for enforcement.
2
+ description: Install or refresh the Subagent Budget Guard statusLine bridge and apply the recommended plugin config.
3
3
  disable-model-invocation: true
4
4
  ---
5
5
 
@@ -11,7 +11,18 @@ Run this command:
11
11
  node "${CLAUDE_PLUGIN_ROOT}/bin/setup.js"
12
12
  ```
13
13
 
14
- Then tell the user to interact with Claude Code once so the statusLine bridge receives fresh session JSON. After that, run:
14
+ This applies the recommended config in Claude settings:
15
+
16
+ ```text
17
+ max_concurrent_subagents=1
18
+ max_subagent_tokens_per_session=100000
19
+ subagent_token_warning_threshold_percent=95
20
+ session_five_hour_budget_percent=25
21
+ absolute_five_hour_ceiling_percent=95
22
+ enforcement_enabled=true
23
+ ```
24
+
25
+ Then tell the user to run `/reload-plugins`, interact with Claude Code once so the statusLine bridge receives fresh session JSON, and run:
15
26
 
16
27
  ```bash
17
28
  node "${CLAUDE_PLUGIN_ROOT}/bin/verify.js" --live