Skip to content

[Upstream PR #193] Stop runaway emergency compaction when compacted_count drifts past history #144

@quangdang46

Description

@quangdang46

Mirrored from upstream 1jehuang/jcodePull Request #193 by @cooleryu
Original state: open
Created: 2026-05-11T12:45:07Z · Updated: 2026-05-12T13:46:21Z
Diff: https://github.com/1jehuang/jcode/pull/193.diff
This issue is an auto-mirrored copy. Comments and edits here are local to quangdang46/jcode — do not expect them to propagate upstream.


Summary

Fixes #175 by making stale compaction state recover instead of re-entering the emergency compaction loop.

This PR:

  • clamps active_messages() with min(compacted_count, messages.len()), so a stale compacted count no longer falls back to replaying the full transcript
  • self-heals stale compacted_count > messages.len() before API message assembly, background compaction application, and hard compaction
  • bounds compaction count advances with saturating arithmetic and messages.len() when caller history is available
  • logs stale persisted compaction state during restore instead of silently clamping it
  • adds four regression tests for the fix(ambient): handle string or u32 for Claude tool parameters (#133) #175 failure mode
  • applies cargo fmt --all formatting needed by the repository CI

Motivation

The issue report shows a long-running coding-agent session getting wedged after emergency compaction. Once compacted_count drifted past the actual message history length, active_messages() returned the full transcript again. That made the next API payload include both the summary and old messages, kept the context above the threshold, and allowed hard compaction to append repeated [Emergency compaction] markers while increasing compacted_count even further.

The important invariant is: if the caller provides the full message list, a compacted count beyond that list cannot mean "all messages are active again". It means the manager has stale state and should recover to an empty active tail.

Changes

  • Treat stale compacted_count as a recoverable state and clamp it to messages.len().
  • Reset stale active-message accounting when the clamp is applied.
  • Preserve the backward-compatible no-history path used by check_and_apply_compaction().
  • Prevent hard/background compaction from advancing compacted_count beyond caller history.
  • Add restore-time warning logs for persisted stale compaction state.
  • Add regression coverage for:
    • API message assembly not replaying the full transcript after a summary
    • active_messages() clamping stale state to an empty active tail
    • token estimation ignoring stale cached active char counts
    • hard compaction not inflating stale counts or appending another emergency block

Test Plan

  • cargo test test_bug_175_ -- --nocapture
    • 4 passed
  • cargo test compaction::tests:: -- --nocapture
    • 30 passed
  • cargo fmt --all -- --check
    • passed
  • cargo check --all-targets --all-features
    • passed

I also installed and tried local clippy. On my macOS machine it stops on pre-existing macOS-only lints in crates/jcode-core/src/stdin_detect.rs; I did not mix that unrelated cleanup into this PR.

Risk

Low to moderate. This changes recovery behavior only when compacted_count is already inconsistent with the caller-provided history. Normal compaction paths should continue to use the same active suffix. The no-history compatibility path is preserved so existing callers of check_and_apply_compaction() still apply completed background compactions.

Maintainer Context

The issue already included a useful local hotfix note. This PR turns that direction into a tested upstream fix and keeps the scope limited to the compaction invariant, recovery points, and regression coverage. It intentionally does not add a session-file migration script; that can remain a separate operational recovery step if maintainers want it.


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions