session: trace resolve/attach/compact points for #2707 wrong-session theory

This commit is contained in:
damocles 2026-07-26 20:52:40 +02:00 committed by mara
commit 1bbc09e9dd
4 changed files with 72 additions and 12 deletions

View file

@ -44,12 +44,21 @@ impl SessionStore {
/// Find the `<uuid>.jsonl` in the project dir whose `customTitle` equals
/// `title` (the value `--name` sets, stored in a `custom-title` event).
///
/// Reads each session file line by line and stops at the first match, so a
/// huge transcript isn't slurped into memory. Returns `None` if no session
/// carries the title or the project dir is absent. Non-`.jsonl` files
/// (including anything already archived to `*.jsonl.archived`) are skipped.
/// Reads each session file line by line, so a huge transcript isn't
/// slurped into memory. Returns `None` if no session carries the title or
/// the project dir is absent. Non-`.jsonl` files (including anything
/// already archived to `*.jsonl.archived`) are skipped.
///
/// Unlike the old first-match-wins scan, this keeps going past the first
/// hit so a *second* same-titled file — which would otherwise resolve to
/// whichever one `read_dir` happens to yield first, i.e. filesystem-order
/// luck — logs a `tracing::warn!` instead of silently picking one. The
/// first match found is still what's returned (unchanged resolution
/// behaviour); this is a diagnostic for the "wrong session resumed"
/// report, not a fix.
#[must_use]
pub fn find_by_title(&self, title: &str) -> Option<PathBuf> {
let mut found: Option<PathBuf> = None;
for entry in std::fs::read_dir(self.project_dir()).ok()?.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("jsonl") {
@ -58,15 +67,27 @@ impl SessionStore {
let Ok(file) = std::fs::File::open(&path) else {
continue;
};
if std::io::BufReader::new(file)
let matches = std::io::BufReader::new(file)
.lines()
.map_while(std::result::Result::ok)
.any(|line| line_sets_title(&line, title))
{
return Some(path);
.any(|line| line_sets_title(&line, title));
if !matches {
continue;
}
match &found {
None => found = Some(path),
Some(first) => {
tracing::warn!(
title,
first = %first.display(),
also = %path.display(),
"find_by_title: multiple session files share this title — \
resuming the first one found (readdir order, not deterministic)"
);
}
}
}
None
found
}
/// Archive the session titled `title` by renaming its backing file
@ -83,12 +104,22 @@ impl SessionStore {
/// [`Error::Io`] if the rename fails.
pub fn archive_by_title(&self, title: &str) -> Result<Option<PathBuf>> {
let Some(path) = self.find_by_title(title) else {
tracing::info!(
title,
"archive_by_title: no session file found for this title"
);
return Ok(None);
};
let mut target = path.clone().into_os_string();
target.push(".archived");
let target = PathBuf::from(target);
std::fs::rename(&path, &target).map_err(Error::Io)?;
tracing::info!(
title,
from = %path.display(),
to = %target.display(),
"archive_by_title: renamed session file"
);
Ok(Some(target))
}
}