Skip to content

fix(ways): pin list context lookup to session id#93

Merged
aaronsb merged 1 commit into
mainfrom
fix/ways-list-context-detection
May 21, 2026
Merged

fix(ways): pin list context lookup to session id#93
aaronsb merged 1 commit into
mainfrom
fix/ways-list-context-detection

Conversation

@aaronsb
Copy link
Copy Markdown
Owner

@aaronsb aaronsb commented May 21, 2026

Summary

  • ways list was misreporting context as 200K on 1M-window sessions when the shell cwd was outside the active session's project.
  • Root cause: detect_project_dir walks up from cwd looking for .claude/settings.json, and matches ~/.claude (the global config) when no closer project marker exists. The transcript lookup then targets a non-existent project dir, errors out, and falls through to the 200K heuristic.
  • Fix: resolve the transcript by session id directly (~/.claude/projects/*/<sid>.jsonl). The session id is already known to ways list and is globally unique, so no project guessing is needed.

Test plan

  • ways list from ~/Projects/aaronsb/anthropic-basecamp/ (cwd outside session project) now reports 1000K ctx instead of 200K ctx
  • ways list from inside the session project still reports correctly
  • cargo test -p ways --release — 70 unit + 11 sim tests pass

`ways list` was misreporting the context window when the shell cwd
was outside the active session's project. The cwd-walking project
detection could land on ~/.claude (global config) when no closer
.claude marker exists, point the transcript lookup at a non-existent
project dir, and fall back to the 200K heuristic — even on opus-4
1M-window sessions.

Resolve the transcript by session id directly (scan
~/.claude/projects/*/<sid>.jsonl), since the session id is already
known by the caller and is globally unique. The cwd-based path
remains for the project-scoped `ways context` entrypoint.
@aaronsb
Copy link
Copy Markdown
Owner Author

aaronsb commented May 21, 2026

Review Summary

What this changes: Replaces cwd-derived project lookup with session-id-pinned transcript discovery for ways list, fixing the 200K-vs-1M context misreport when the shell cwd is outside the active session's project.

Assessment: Fix is correct and minimal. The dual-mode get_context_inner is a reasonable refactor for this scope. Found one functional edge case worth a small follow-up and a few smaller items.


Blockers

None.


Suggestions

1. find_transcript_by_session swallows real read errors as None (context.rs:251-262)

for entry in std::fs::read_dir(&projects_root).ok()? {
    let entry = entry.ok()?;

The outer ok()? is correct (no projects dir → fall through cleanly). But the inner entry.ok()? exits the whole loop on the first unreadable DirEntry, returning None even if the target transcript is later in the iteration. In practice this is fine — ~/.claude/projects entries are user-owned dirs — but the failure mode is silent.

for entry in std::fs::read_dir(&projects_root).ok()?.flatten() {
    let candidate = entry.path().join(&filename);
    if candidate.is_file() {
        return Some(candidate);
    }
}

.flatten() skips bad entries and keeps scanning. Same line count, more robust.

2. The HOME-blindness in detect_project_dir is still a latent bug for ways context (util.rs:20-34)

Sidestepping it for ways list is the right call for this PR — small, surgical fix matching the reported symptom. But ways context (no args, no session pin) still walks up from cwd and will land on ~/.claude when invoked from ~/Projects/..., returning /home/bockeliea as the project. This produces the same wrong-transcript-read for the standalone ways context command.

Two reasonable paths for a follow-up:

  • Have detect_project_dir reject $HOME as a project root (it shouldn't be one — global .claude/ lives there by definition).
  • Or: change ways context to also auto-detect the session id when no project is given, then route through get_context_for_session. This makes session-id the canonical key everywhere and keeps detect_project_dir for callers that genuinely need a project path (lint, stats, permissions, template).

I'd lean toward the second — session-id is the actual identity here, project dir is just an index into where transcripts live. But either is fine as a separate PR. Leaving it out of this one is justified.

3. Test gap for find_transcript_by_session

Easy to unit-test with a temp HOME override:

Not a blocker — the integration path is exercised by manual testing in the PR body — but the function is small enough that 4 cheap unit tests would lock in the contract.


Nits

4. Naming: get_context_inner (context.rs:40)

The _inner suffix is conventional in Rust for private impls but signals "trivial wrapper." This one isn't trivial — it has two distinct discovery paths. Consider resolve_context_info or get_context_with_strategy. Minor.

5. Doc comment on get_context (context.rs:22-28)

The doc says "When session_id is provided..." but get_context doesn't take a session_id — that's get_context_for_session. The fallback-behavior sentence belongs on get_context_for_session or get_context_inner. Currently it reads as if get_context has hidden behavior it doesn't.

6. Determinism question raised in review prompt

std::fs::read_dir order is filesystem-dependent (not sorted). If two project dirs ever held the same session-id filename, the result would be non-deterministic. Session IDs are UUIDs so collision is effectively impossible in practice, and Claude Code writes each session to exactly one project dir. Worth noting in the doc comment that the function relies on session-id uniqueness, but no code change needed.


Unchanged callers — correctly left alone

  • cmd/show/mod.rs:50 — runs in a hook with CLAUDE_PROJECT_DIR set; get_context(Some(&project_dir)) is correct.
  • cmd/rethink.rs — uses session::detect_context_window_for(project, sid), a different path. Unaffected.
  • cmd/context.rs::run — top-level ways context command. Still goes through the cwd-walk path; see suggestion attend: remaining work from ADR-113/114 implementation #2.

Why this is sound

  • Session IDs are UUIDs and Claude Code writes each session's transcript to exactly one project dir, so the iteration in find_transcript_by_session finds the right file on the first match.
  • The fallback heuristic in list.rs:67-71 is preserved, so a missing transcript still produces a usable (if approximate) answer rather than crashing the command.
  • The get_context public signature is unchanged — no churn for the hook caller in show/mod.rs.
  • Tests still pass (70 unit + 11 sim per PR body); no behavior change for ways context or ways show paths.

@aaronsb aaronsb merged commit 66708ab into main May 21, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant