You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Mirrored from upstream 1jehuang/jcode — Pull 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
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.
Need help on this PR? Tag @codesmith with what you need.
Summary
Fixes #175 by making stale compaction state recover instead of re-entering the emergency compaction loop.
This PR:
active_messages()withmin(compacted_count, messages.len()), so a stale compacted count no longer falls back to replaying the full transcriptcompacted_count > messages.len()before API message assembly, background compaction application, and hard compactionmessages.len()when caller history is availablecargo fmt --allformatting needed by the repository CIMotivation
The issue report shows a long-running coding-agent session getting wedged after emergency compaction. Once
compacted_countdrifted 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 increasingcompacted_counteven 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
compacted_countas a recoverable state and clamp it tomessages.len().check_and_apply_compaction().compacted_countbeyond caller history.active_messages()clamping stale state to an empty active tailTest Plan
cargo test test_bug_175_ -- --nocapturecargo test compaction::tests:: -- --nocapturecargo fmt --all -- --checkcargo check --all-targets --all-featuresI 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_countis 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 ofcheck_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.
Need help on this PR? Tag
@codesmithwith what you need.