fix(hive-claude): report Progress.created on first-turn overflow, compacted only when compact succeeded

This commit is contained in:
müde 2026-07-05 23:42:57 +02:00
commit dd51d02bc2

View file

@ -66,19 +66,25 @@ impl<P: CompactionPolicy> InfiniteSession<P> {
/// [`Error::PromptTooLong`]; rate-limit / auth / hard failures propagate
/// unchanged for the caller to handle.
pub async fn run(&self, config: &Config, prompt: &str, sink: &impl Sink) -> Result<Progress> {
// Resolve resume-vs-create once, up front, so `created` reflects the
// session state at the START of the run: the reactive-retry path can't
// read it from the retry (the session exists by then).
let existed = self.store.find_by_title(&self.name).is_some();
let meter = TelemetrySink::new(sink);
let created = match self.attempt(config, prompt, &meter).await {
let created = match self.attempt(config, prompt, &meter, existed).await {
Ok(created) => created,
Err(Error::PromptTooLong) => {
// The session is already past the window — no turn can run on
// it and the detail is gone (no checkpoint possible). Compact,
// then retry the same prompt once; the retry is the answering
// turn, so its telemetry is what we report.
// turn, so its telemetry is what we report. The failed attempt
// created/resumed the session, so the retry resumes it, and
// `created` still reflects the pre-run state.
self.compact(config, sink).await?;
let retry = TelemetrySink::new(sink);
let created = self.attempt(config, prompt, &retry).await?;
self.attempt(config, prompt, &retry, true).await?;
return Ok(Progress {
created,
created: !existed,
compacted: true,
telemetry: retry.snapshot(),
});
@ -91,12 +97,15 @@ impl<P: CompactionPolicy> InfiniteSession<P> {
// says it's due, checkpoint (best-effort) then compact.
if self.policy.should_compact(telemetry.usage()) {
if let Some(checkpoint) = self.policy.checkpoint_prompt() {
let _ = self.attempt(config, checkpoint, sink).await;
let _ = self.attempt(config, checkpoint, sink, true).await;
}
let _ = self.compact(config, sink).await;
// Only claim a compaction if it actually succeeded — the flag feeds
// stats + the auto-reset watermark, so a failed best-effort
// `/compact` must not report that the context shrank.
let compacted = self.compact(config, sink).await.is_ok();
return Ok(Progress {
created,
compacted: true,
compacted,
telemetry,
});
}
@ -121,22 +130,29 @@ impl<P: CompactionPolicy> InfiniteSession<P> {
}
}
/// One resume-or-create turn. Uses the store to pick resume vs. create up
/// front (avoiding a wasted resume-miss spawn), and still self-heals if the
/// backing file vanished between the check and the run. Returns whether a
/// fresh session was created.
async fn attempt(&self, config: &Config, prompt: &str, sink: &impl Sink) -> Result<bool> {
let exists = self.store.find_by_title(&self.name).is_some();
let attach = if exists {
/// One resume-or-create turn. `existed` is the caller's up-front
/// resume-vs-create decision (whether the titled session was on disk before
/// the run) — passing it in rather than re-checking keeps `created`
/// reporting consistent across the reactive-retry path. Still self-heals if
/// the backing file vanished between the check and the run. Returns whether
/// a fresh session was created.
async fn attempt(
&self,
config: &Config,
prompt: &str,
sink: &impl Sink,
existed: bool,
) -> Result<bool> {
let attach = if existed {
Attach::Resume(self.name.clone())
} else {
Attach::Create(self.name.clone())
};
match Claude::run(config, &attach, prompt, sink).await {
Ok(()) => Ok(!exists),
Ok(()) => Ok(!existed),
// We thought it existed but the resume missed (raced an archive) —
// self-heal by creating.
Err(Error::SessionNotFound) if exists => {
Err(Error::SessionNotFound) if existed => {
Claude::run(config, &Attach::Create(self.name.clone()), prompt, sink).await?;
Ok(true)
}