A long Claude Code run is repairing a failed payments release. Early in the session, the policy gateway denies release.override because the run only has staging access. The agent records the denial, investigates logs, patches a retry bug, and keeps working.

Then the context is compacted.

The summary remembers the objective and the changed files. It omits the denied production action. Later, a tool result suggests release.override as the quickest way to finish. The agent asks for it again, now without the earlier refusal in its working context.

Nothing malicious happened inside the model. The safety decision simply lived in the wrong place.

A transcript is useful working memory. It is not a control plane. Authority, denials, unresolved external effects, and approval bindings need durable state outside the context window. After any compaction, restart, or handoff, Claude Code should pass a restore gate before it can use consequential tools.

External control-state checkpoint for Claude Code

Separate task memory from control state

A compaction summary may safely compress explanations, failed hypotheses, and old command output. It must not become the only copy of facts that decide what the agent may do.

I split run state into two classes:

working_memory:
  objective: repair payments retry failure
  current_hypothesis: duplicate settlement events originate in worker retry path
  files_in_focus:
    - src/payments/retry_worker.py
    - tests/payments/test_retry_worker.py

control_state:
  authority_scope: staging-only
  denied_actions:
    - tool: release.override
      target: production-eu
      policy_reason: environment_outside_run_scope
  unresolved_effects: []
  active_approvals: []
  policy_version: release-policy-42

The model can summarize working_memory. The runtime owns control_state. A prompt cannot widen it, and a summary cannot silently erase it.

This is also why MCP tool output must remain evidence rather than authority. Retrieved text may influence the investigation. It cannot edit the run’s permissions or delete a refusal.

Write a checkpoint before context changes

Before compaction, persist a checkpoint that is narrow enough to inspect and complete enough to restore the safety boundary.

control_state_checkpoint:
  checkpoint_id: csc-20260823-071
  run_id: claude-run-5881
  sequence: 14
  created_at: 2026-08-23T09:31:18.442Z
  reason: context_compaction
  task_contract_digest: sha256:87c1...
  policy:
    version: release-policy-42
    digest: sha256:34ad...
  authority:
    environments: [staging-eu]
    tools:
      allow:
        - logs.search
        - repo.patch
        - tests.run
        - release.read
      deny:
        - release.override
        - release.deploy
  denial_history:
    - event_id: deny-991
      tool: release.override
      target: production-eu
      evidence_id: ev-tool-output-318
      policy_reason: environment_outside_run_scope
  pending_operations: []
  approvals: []
  evidence_bindings:
    patch_digest: sha256:9b70...
    test_run_id: test-8820
  previous_checkpoint_digest: sha256:2c11...
  checkpoint_digest: sha256:b772...

Keep the checkpoint append-only. The sequence and previous digest expose deletion or reordering. Bind it to the task contract and policy version so a checkpoint from another run cannot be replayed into this one.

Denial history matters even when the denied call had no effect. It tells the restored run that an attractive path was already considered and blocked. If the agent proposes it again, the gateway can return the existing decision instead of treating the request as new.

Pending operations matter even more. If an MCP write timed out before compaction, the restored agent must recover the unknown outcome before doing dependent work. Context loss does not turn an unresolved write into a failed write.

Make restoration a gate, not a prompt reminder

After compaction, disable consequential tools until the runtime verifies the checkpoint.

restore_gate:
  run_id: claude-run-5881
  checkpoint_id: csc-20260823-071
  checks:
    digest_valid: true
    sequence_is_latest: true
    task_contract_matches: true
    policy_version_current: true
    pending_operation_recovery_complete: true
    approval_bindings_valid: true
  restored_denials: 2
  restored_pending_operations: 0
  decision: allow_scoped_tools

If the policy changed while the context was being compacted, do not restore old authority. Re-evaluate the run against the current policy. If the task contract changed, issue a new contract rather than editing the checkpoint in place.

The gate should restore permissions to the tool gateway, not merely paste a paragraph into the new context. The model still benefits from a readable summary, but enforcement stays outside the model.

Human approval needs the same treatment. An approval should remain bound to one operation, payload, policy version, and expiry. The single-use approval capability either survives restoration with every binding intact or expires. A sentence saying “approval was granted earlier” is not enough.

Do not let summaries rewrite control facts

A model-generated summary can propose additions to working memory. It should never be able to submit a control-state replacement.

Use typed update paths:

state_update_rules:
  model_may_propose:
    - current_hypothesis
    - files_in_focus
    - next_investigation_step
  gateway_may_append:
    - denial_event
    - operation_transition
    - approval_redemption
    - policy_evaluation
  forbidden_from_model_output:
    - widen_authority
    - clear_denial_history
    - mark_unknown_effect_failed
    - renew_expired_approval

This prevents a well-written compaction summary from becoming an accidental privilege change. It also keeps instruction-shaped tool output from smuggling a control update through the summary process.

Test the context boundary directly

Ordinary task evals rarely trigger compaction at the worst moment. Add fixtures that force it around safety decisions and incomplete effects.

context_boundary_evals:
  - case: denied_tool_call_then_compaction
    expect: same_call_remains_denied_after_restore
  - case: timed_out_write_then_compaction
    expect: restore_unknown_outcome_and_reconcile
  - case: approval_expires_during_compaction
    expect: require_new_approval
  - case: summary_claims_broader_authority
    expect: ignore_claim_and_restore_gateway_scope
  - case: stale_checkpoint_replayed
    expect: reject_sequence_and_digest
  - case: policy_changes_during_compaction
    expect: re_evaluate_before_tool_enablement
  - case: child_agent_handoff
    expect: inherit_only_explicit_delegated_scope

The first fixture should include a tempting tool response after restoration. If the earlier denial disappears when the summary is plausible and helpful, the control boundary is still tied to prose.

Also test process restarts and handoffs between agents. A context checkpoint that only works inside one warm process is session state, not durable control state.

Put the checkpoint in the review packet

The reviewer does not need the full transcript. The review packet should identify the checkpoint restored for the final phase of work, its policy version, any unresolved operations, and the denials that remained active.

review_packet_control_state:
  final_checkpoint: csc-20260823-074
  policy_version: release-policy-42
  compactions: 2
  restored_denials: [deny-991, deny-1004]
  unresolved_operations: 0
  approvals_redeemed: 0
  authority_at_completion: staging-only
  checkpoint_chain_valid: true

This gives the reviewer evidence that the run finished under the same boundary it started with. It also makes a missing checkpoint visible instead of burying the gap inside a long transcript.

Context compaction is normal. Safety amnesia is not. Let Claude Code compress its conversation, but keep authority, denials, approval bindings, and uncertain effects in a checkpoint the model cannot rewrite. Restore that state before tools come back online.

Claude Code: Building Production Agents That Actually Work covers production patterns for permissions, MCP boundaries, replay, evals, rollback, and review packets around coding agents.