fix: a pinned session's telemetry names the picked workspace

Merged
RA
14h ago
+513-8

Issue for this PR

Closes #1671

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

An IDE extension can start serve with a workspace pin. The pin decides identity, skills, memory and tool routing, but a turn's telemetry rows read only the project's on-disk link, where the pin is never written. So a pinned session sent no workspace_id, or the id of the project's own link.

resolveTurnBinding in state.ts gives a turn's workspace the way routing does: the pin first, then the on-disk link. turnBinding resolves it once per turn, and prompt.ts uses that one verdict for attached files and for the turn's rows.

  • A pin that can't be honoured names no workspace, as routing refuses it. It never falls back to the link.
  • Unpinned turns keep the local cache read, which makes no network call.
  • Past the validation TTL the pin's verdict is a network call. A turn waits for it once, at most 3 seconds, then sends its rows without a workspace. Routing joins that request while it is in flight.

How did you verify your code works?

Tests. pinned-workspace-loop.test.ts runs the real prompt loop. Before the fix a pinned session stamped the link or nothing, a refused pin stamped the link, and attached files each resolved the pin again. routing-pin.test.ts covers a malformed pin, a directory outside the root, the wait, and one verdict per turn.

E2E. The released VS Code extension 0.15.0 in code-server, signed in, with its own selection pinning serve. CLI rows went to a local catcher.

Project's on-disk linkv0.12.9This branch
A different workspacethe link's idthe pinned id
Noneno idthe pinned id

That held on session_start, task_classified, task_outcome_signal, agent_outcome, session_end, and tool_call where a tool failed. Not exercised: via_workspace, which needs the engine.

Suites. test/altimate/workspace and test/altimate/telemetry pass locally and typecheck is clean. The full suite runs in CI.

Cost side effects

None. A pinned turn reads the existing validation memo. Past its TTL, or for a pin the account can't see, which is never memoized, it is one validation request per turn.

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR
  • I checked for cost side effects (model calls, queries or cloud resources that grow with user data) and described them, or wrote "none"

🤖 Generated with Claude Code

https://claude.ai/code/session_01VEmKQswEEpkYFmMEngESx8


Summary by cubic

Fixes a pinned session's telemetry rows naming the wrong workspace, or none. Telemetry read only the project's on-disk link, but the IDE extension's pin is never written there, so a pinned session stamped no workspace_id or the link's id. resolveTurnBinding resolves a turn's workspace the way routing does: the pin first, then the on-disk link.

Behavior

  • A pin that cannot be honored names no workspace, never falling back to the project's link.
  • Unpinned turns keep the local cache read, with no credentials and no network.
  • A turn resolves its workspace once: past the validation TTL the first read waits at most 3 seconds for the pin's verdict, and all later reads in the turn — the attached files and the loop — share that answer. The routing read later in the turn joins the same request, so a failed validation doesn't run twice.
  • Each new turn of a session resolves its own workspace; a later turn never inherits the first turn's verdict.

Summary by CodeRabbit

  • Bug Fixes
    • Session telemetry and attachment reads now consistently use the workspace selected for the prompt turn. A valid workspace pin takes precedence over the project’s linked workspace.
    • When no pin is set, the project’s linked workspace is used. If a pin is invalid or cannot be verified in time, records are no longer attributed to the project’s linked workspace as a fallback.
    • Workspace selection is resolved independently for each prompt turn, including later turns in the same session.