HookStack
Back to catalogue
ContextSessionStart· compact

Re-inject context after compaction

Conventions survive a context compaction

Re-injects the project conventions and critical context into the session after Claude Code compacts the context window, preventing the loss of important information.

What does the Re-inject context after compaction hook do?

Re-inject context after compaction is a Claude Code SessionStart hook matching compact. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Conventions survive a context compaction.

As a SessionStart hook it runs after the action, reacting to what just happened rather than blocking it. Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.

Use cases

  • Recall the project conventions (package manager, code style) after compaction
  • Re-inject the current sprint or critical tasks
  • Recall important test or build commands

Tags

#context#compaction#session#conventions

settings.json fragment

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/reinject-after-compact.mjs",
            "type": "command"
          }
        ],
        "matcher": "compact"
      }
    ]
  }
}

Script · .claude/hooks/reinject-after-compact.mjs

#!/usr/bin/env node
// @hookstack session-start-reinject-after-compact
// Réinjecte le contexte sauvegardé avant la dernière compaction (SessionStart)
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";

export function run(
	input,
	{
		exists = existsSync,
		readFile = readFileSync,
		readdir = readdirSync,
		backupDir = "/tmp/claude-compact-backups",
	} = {},
) {
	const sessionId = input.session_id ?? "";
	if (!exists(backupDir)) return null;

	// Cherche le backup de la session courante en priorité, sinon le plus récent
	let backupFile = sessionId ? join(backupDir, `${sessionId}.json`) : null;

	if (!backupFile || !exists(backupFile)) {
		const files = readdir(backupDir)
			.filter((f) => f.endsWith(".json"))
			.map((f) => ({
				f,
				mtime: new Date(
					readFile(join(backupDir, f), "utf8")
						.split('"saved_at":"')[1]
						?.split('"')[0] ?? 0,
				),
			}))
			.sort((a, b) => b.mtime - a.mtime);
		backupFile = files[0] ? join(backupDir, files[0].f) : null;
	}

	if (!backupFile || !exists(backupFile)) return null;

	try {
		const { summary, saved_at } = JSON.parse(readFile(backupFile, "utf8"));
		if (summary) {
			return `## Contexte de la session précédente (avant compaction du ${saved_at})\n\n${summary}\n`;
		}
	} catch {
		// Fichier corrompu — ignorer silencieusement
	}
	return null;
}

/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
	const input = JSON.parse(readFileSync(0, "utf8"));
	const result = run(input);
	if (result) process.stdout.write(result);
}

Learn more

Related hooks