How the Compaction Hook Detects WIP Tasks Before Context Compaction in Claude-Code-Harness

The compaction hook detects WIP tasks by scanning Plans.md for cc:WIP or [in_progress] markers during the pre-compact-save phase, serializing the findings to a JSON snapshot, and re-injecting them into the system message after compaction completes.

The claude-code-harness implements a safeguard mechanism to prevent accidental loss of in-progress work during context window compaction. When a user triggers the compact command, the harness executes the pre-compact-save hook—implemented in go/internal/hookhandler/pre_compact_save.go—to analyze the current project's task state before discarding any session context.

Pre-Compact Detection Flow

The detection process follows a strict pipeline that transforms raw markdown table data into a structured artifact. This ensures the system captures a complete snapshot of active work before the compaction phase begins.

Locating and Reading Plans.md

The hook first resolves the project root (or a custom plansDirectory if configured) to locate the Plans.md file. It reads the entire file content and splits each line into discrete rows representing individual tasks. This raw tabular data serves as the input for the WIP detection logic.

Parsing WIP Status Markers

The core detection uses a case-insensitive regular expression to identify work-in-progress status. According to the source code in pre_compact_save.go (lines 434–440), the harness compiles the following pattern:

reWip := regexp.MustCompile("(?i)`?cc:WIP`?|\\[in_progress\\]")
wipRows := filterRows(planRows, reWip)

This regex matches both the inline code format `cc:WIP` (with optional backticks) and the bracketed tag [in_progress], ensuring flexibility in how users format task statuses.

Counting and Extracting Task Details

Once filtered, the hook extracts both quantitative and qualitative data through dedicated functions:

  • countWIP(planRows) (line 353): Returns the total number of rows matching the WIP pattern
  • getWIPTasks(planRows) (line 341): Extracts human-readable task titles for the snapshot

These functions isolate the active tasks while preserving their descriptive context for later reporting.

Building the Pre-Compact Snapshot

If WIP tasks exist, the hook constructs a summary string and serializes the data to precompact-snapshot.json. As implemented in pre_compact_save.go:

summaryParts := []string{}
if wipCount > 0 {
    summaryParts = append(summaryParts, fmt.Sprintf("%d WIP", wipCount))
}
prevSummary := "Before compaction: " + strings.Join(summaryParts, ", ")

artifact := struct {
    Summary  string   `json:"summary"`
    WIPTasks []string `json:"wipTasks"`
}{
    Summary:  prevSummary,
    WIPTasks: wipTasks,
}

This artifact persists to disk in the state directory, creating a durable record of active tasks that survives the context compaction process.

Post-Compact Re-injection Mechanism

After compaction completes, the corresponding post-compact hook—implemented in both scripts/hook-handlers/post-compact.sh (lines 246–300) and go/internal/event/post_compact.go (lines 337–358)—retrieves the snapshot and reconstructs the warning context.

The shell handler reads the JSON artifact:

PRECOMPACT_SNAPSHOT="${STATE_DIR}/precompact-snapshot.json"

get_precompact_context() {
  if [[ -f "$PRECOMPACT_SNAPSHOT" ]]; then
    wip_tasks=$(jq -r '.wipTasks[]' "$PRECOMPACT_SNAPSHOT" | paste -sd ', ' -)
    echo "Pre-compaction WIP tasks: ${wip_tasks}"
  fi
}

PRECOMPACT_CONTEXT="$(get_precompact_context)"
SYSTEM_MESSAGE="[PostCompact Re-injection] Context was just compacted. ${PRECOMPACT_CONTEXT}"

This re-injected message surfaces the previously identified WIP tasks immediately after compaction, alerting the user to active work that may require attention in the newly compressed context.

Key Implementation Files

The WIP detection logic spans multiple files across the repository:

Summary

  • The pre-compact-save hook intercepts the compact command to analyze Plans.md before context deletion
  • Regex pattern (?i)?cc:WIP?|\[in_progress\] identifies WIP tasks regardless of case or formatting variations
  • getWIPTasks and countWIP functions extract task titles and totals for the snapshot
  • The precompact-snapshot.json artifact preserves WIP state through the compaction process
  • The post-compact hook re-injects WIP warnings into the system message after context compression completes

Frequently Asked Questions

What happens if no WIP tasks are detected during pre-compact?

If countWIP returns zero, the hook still generates a snapshot but omits the WIP component from the summary string. The post-compact phase will skip the WIP warning and surface only other relevant metadata, such as recent edit counts.

How does the post-compact hook access the WIP task data?

The post-compact hook reads the precompact-snapshot.json file located in the state directory (${STATE_DIR}). It uses jq to extract the wipTasks array and formats the entries into a comma-separated list for the re-injection message.

Can the WIP detection regex match custom status markers?

The current implementation in pre_compact_save.go uses a hardcoded regex targeting specifically cc:WIP and [in_progress]. Users must conform to these markers for detection; customizing the pattern requires modifying the source regex in pre_compact_save.go lines 434–440.

Where is the pre-compact snapshot stored?

The snapshot persists as precompact-snapshot.json in the harness state directory, typically referenced via the ${STATE_DIR} environment variable. Both the pre-compact Go code and post-compact shell script rely on this path to communicate WIP state across the compaction boundary.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →