Skip to content

[P2] Keep bare \new in the thread's bound project #36

Open

Description

@MatthewS65537

Severity: P2 · Confidence: medium · Area: Commands

Where

Problem

Bare \new ("Fresh session in this thread") uses ctx.state.defaultDir(ctx.channelId, ctx.cwd) — the global current project or a channel pin — not ctx.thread?.projectDir. Every boot resets the current project to the launch cwd, and a \cd in any other thread moves it. Other thread-scoped commands (\model, \sessions, \usage, \cmd, \restart) prefer ctx.thread?.projectDir.

Failure scenario

Thread bound to /projB; bridge restarts in /launch; \new in that thread creates the session in /launch, and subsequent file-editing prompts run in the wrong repo.

Evidence

Local probe cc_mnt/review-20261011/commands/new-and-project.test.ts prints "✨ Fresh session … in /launch" for a thread bound to /projB. Confidence medium only because intent could be argued; the help text says "in this thread".

Suggested fix

const dir = ctx.state.channelProject(ctx.channelId) ?? ctx.thread?.projectDir ?? ctx.state.defaultDir(ctx.channelId, ctx.cwd).

Acceptance

Bare \new in a thread bound to /projB (unpinned channel) creates the session in /projB.


Found in the 2026-10-11 codebase review of commit 22ce7ee (typecheck clean, 1,142/1,142 tests passing). Probe paths refer to the maintainer's local, gitignored review ledger.

Activity

  1. MatthewS65537 commented on Oct 11, 2026

    @MatthewS65537
    OwnerAuthor

    Proposed fix

    Approach: Bare \new should start the fresh session in the project the thread is already bound to. This matches the rule start.ts:113-117 states ("existing threads keep their own bound dir") and the ctx.thread?.projectDir ?? defaultDir fallback every other command uses (\model, \sessions, \usage, \cmd, \restart). A channel pin still wins, because the \pin contract is "new sessions here start in it". An unbound thread keeps today's behavior.

    Changes:

    src/commands/handlers.ts, \new (line 640):

    -    const dir = ctx.state.defaultDir(ctx.channelId, ctx.cwd);
    +    // Same project as the thread (a pin still wins); unbound threads use the channel default.
    +    const dir = ctx.state.channelProject(ctx.channelId) ?? ctx.thread?.projectDir ?? ctx.state.defaultDir(ctx.channelId, ctx.cwd);
         await rebindThread(ctx, dir, "✨", { teardown: false });

    Help detail (line 631), so the bare form is explicit:

    -  detail: "`\\new /abs/path` also switches the current project. Accepts `~` paths. The previous session is kept (\\resume recovers it).",
    +  detail: "Bare `\\new` stays in this thread's project; `\\new /abs/path` also switches the current project. Accepts `~` paths. The previous session is kept (\\resume recovers it).",

    Tests: in test/commands.test.ts, next to "\new unbinds only…":

    • A thread is bound to /projB and state.setCurrentProject("/launch") simulates a boot. Bare \new must produce getThread("C1:T1").projectDir === "/projB", and currentProjectDir must stay /launch. My probe cc_mnt/review-20261011/commands/new-and-project.test.ts currently shows /launch.
    • With state.setChannelProject("C1", "/pinned") and a thread bound to /projB, bare \new uses /pinned.
    • An unbound thread uses defaultDir (unchanged).

    Risks / interactions:

    Alternatives considered:

    • Stop resetting currentProjectDir on boot: fixes only the restart case, not the "another thread \cd'd elsewhere" case, and reverses a deliberate design.

    Effort: S. Confidence: high


    Proposal only, not implemented. Sketches are against 22ce7ee.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions