Skip to content

fix: workspace telemetry follow-up: kill-switch guard, origin on first events, review fixes - #1705

Open
ralphstodomingo wants to merge 3 commits into
mainfrom
fix/workspace-telemetry-followups
Open

ralphstodomingo wants to merge 3 commits into
mainfrom
fix/workspace-telemetry-followups

Conversation

@ralphstodomingo

@ralphstodomingo ralphstodomingo commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #1671
Closes #1698

Type of change

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

What does this PR do?

Follow-up to #1672 and #1699, from their reviews.

  • Kill switch. With ALTIMATE_DISABLE_WORKSPACE on, routing never reads the IDE extension's pin, yet a turn's rows still validated it and waited up to 3 s. Now they skip it and name the project's link, as routing does.
  • origin on first_prompt_sent and first_job_completed: onboarding or workspace. Rows from older clients have none and are onboarding rows. Not source: the envelope stamps source on every row, and an event's own field would overwrite it.
  • Smaller fixes.
    • A subagent's turn isn't the workspace's first job.
    • A noReply prompt's attachments skip the pin's validation.
    • A directory attachment names the turn's workspace.
    • The mark is keyed by the tenant the turn resolved under.
    • A turn that ends before its loop clears its verdict.
    • An unwritable state directory logs once.

Claims, residuals and the complexity delta are in the review log comment.

How did you verify your code works?

Each fix has a test that fails without it, except the early-end clears and the once-only warning (R6 on the log). The tests are in routing-pin.test.ts, first-events.test.ts and onboarding.test.ts. The prompt-loop ones are in pinned-workspace-loop.test.ts and workspace-first-events-loop.test.ts, which now run with the workspace feature on. test/session, test/altimate/workspace and test/altimate/telemetry pass locally. The exceptions are two timing tests in prompt.test.ts (the exit flush, and concurrent loop callers at its 3 s limit), which fail the same way with main's source. Typecheck, Marker Guard, branding and the tracker-leak check pass.

Cost side effects

None. With the kill switch on, a turn sends one request fewer.

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

Follows up on the workspace telemetry PRs with fixes from their reviews. With ALTIMATE_DISABLE_WORKSPACE set, a turn's rows no longer validate the IDE extension's pin or wait up to 3 s on it; they name the project's link, as routing does. first_prompt_sent and first_job_completed now carry origin (onboarding or workspace), and rows from older clients — which have none — are treated as onboarding rows. The pin's verdict now carries the credential it was validated under, so a switch during validation can't key a first-event claim under another tenant.

Closes #1671 and #1698.

Smaller Fixes

  • A subagent's turn is not the workspace's first job; the parent's task call counts it.
  • noReply prompts' attachments skip the pin's validation and wait.
  • Directory attachments name the turn's workspace.
  • The claim mark is keyed by the tenant the turn resolved under.
  • A turn that ends before its loop clears its verdict.
  • An unwritable state directory logs once per process.

Written for commit 0909886. Summary will update on new commits.

View guided diff Turn on auto-fix

Summary by CodeRabbit

  • Bug Fixes
    • Improved workspace attribution for first-prompt and first-job activity, including projects linked to different accounts and cases where workspace pins are unavailable.
    • File and directory attachments now follow the applicable workspace binding, with consistent behavior when workspace features are disabled or pin validation is delayed.
    • Subagent activity no longer counts as a workspace’s first completed job.
  • Documentation
    • Clarified how onboarding and workspace events are distinguished, which prompts and jobs count, and how workspace scope and legacy events are handled.

ralphstodomingo and others added 2 commits November 10, 2026 18:04
With ALTIMATE_DISABLE_WORKSPACE on, routing never reads the pin, yet a turn's rows waited up to 3 s on a validation nothing used. They now name the project's link at once, as routing does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VEmKQswEEpkYFmMEngESx8
first_prompt_sent and first_job_completed carry origin (onboarding or workspace); rows without it are onboarding. Also: a subagent's turn is no job, a noReply attachment skips the pin wait, a turn ended early clears its verdict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VEmKQswEEpkYFmMEngESx8
@ralphstodomingo ralphstodomingo self-assigned this Nov 10, 2026
@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

Turn workspace bindings now carry account scope into prompt and attachment attribution. First-prompt and first-job telemetry records whether an event came from onboarding or workspace activity. Job-completion reporting excludes child sessions, and the telemetry documentation and tests cover the updated behavior.

Changes: Workspace telemetry

Layer / File(s) Summary
Turn binding and attachment attribution
packages/opencode/src/altimate/workspace/state.ts, packages/opencode/src/session/prompt.ts, packages/opencode/test/altimate/workspace/routing-pin.test.ts, packages/opencode/test/session/pinned-workspace-loop.test.ts
Turn binding resolution returns account-scoped bindings and applies pin and feature-toggle behavior. Prompt and attachment paths use validated or unvalidated bindings as appropriate. Tests cover binding scope, pin outcomes, attachment attribution, and the workspace feature toggle.
First-event scope and origin
packages/opencode/src/altimate/telemetry/index.ts, packages/opencode/src/altimate/workspace/first-events.ts, packages/opencode/src/altimate/plugin/onboarding-telemetry.ts, packages/opencode/src/session/prompt.ts, packages/opencode/test/altimate/telemetry/onboarding.test.ts, packages/opencode/test/altimate/workspace/first-events.test.ts, packages/opencode/test/session/workspace-first-events-loop.test.ts, docs/docs/reference/telemetry.md
First-event claims accept the turn’s account scope and telemetry rows include an origin. Job-completion reporting excludes child sessions. Tests and documentation cover origins, claim scope, and completion criteria.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant SessionPrompt
  participant turnBinding
  participant resolveTurnBinding
  participant readPin
  SessionPrompt->>turnBinding: Request turn binding
  turnBinding->>resolveTurnBinding: Resolve directory binding
  resolveTurnBinding->>readPin: Check pin when workspace routing is enabled
  readPin-->>resolveTurnBinding: Return pin outcome and account scope
  resolveTurnBinding-->>turnBinding: Return scoped binding or null
  turnBinding-->>SessionPrompt: Provide binding for prompt or attachment attribution
Loading

Possibly related PRs

Suggested reviewers: sahrizvi

Merge Risk: Low · up to 09098

Rare concurrent prompts or account changes can misattribute workspace telemetry and suppress a first-event row. The change is mergeable with awareness of these limitations.

Pre-merge checks: Passed 5

Check name Status Explanation
Title check Passed The title identifies the workspace telemetry follow-up and names key changes: the kill-switch guard and event origins. “Review fixes” is broad, but the title remains clear and related to the main chan…
Description check Passed The description covers the required sections. It identifies linked issues, describes the changes and verification, states there are no cost side effects, and completes the checklist.
Linked Issues check Passed Issues #1671 and #1698 are closed. They provide historical context only, so no active linked-issue coding requirements apply.
Out of Scope Changes check Passed The changes address the reported workspace pin attribution and first-event telemetry issues. The related state handling, prompt handling, tests, and telemetry documentation support those changes. The …
Docstring Coverage Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentati…
Full details: Title check

Explanation

The title identifies the workspace telemetry follow-up and names key changes: the kill-switch guard and event origins. “Review fixes” is broad, but the title remains clear and related to the main changes.

Full details: Out of Scope Changes check

Explanation

The changes address the reported workspace pin attribution and first-event telemetry issues. The related state handling, prompt handling, tests, and telemetry documentation support those changes. The available whole-PR summary shows no unrelated changes.

Full details: Docstring Coverage

Explanation

Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 10 functions across 10 files. (1 skipped: 1 unsupported.)

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

A rabbit checks the workspace trail,
Then marks each prompt with origin clear.
A parent job completes its run,
While child turns leave that mark undone.
The burrow logs the scope it knew,
And onboarding gets its label too.

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7c3feab7-efcd-4520-826d-0ec4053e17b3
Commits

Reviewing files that changed from the base of the PR and between 1341f81 and 0909886.

Files selected for processing: 4

  • docs/docs/reference/telemetry.md
  • packages/opencode/src/altimate/telemetry/index.ts
  • packages/opencode/src/altimate/workspace/state.ts
  • packages/opencode/test/altimate/workspace/routing-pin.test.ts
Files skipped from review as they are similar to previous changes: 1

  • packages/opencode/src/altimate/telemetry/index.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

@ralphstodomingo

ralphstodomingo commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor Author

Review log

Claims for review of this PR, numbered so a round can cite them. A finding is a reproducible trace that breaks one; the residuals are accepted trade-offs.

Claims

  • C1. With ALTIMATE_DISABLE_WORKSPACE on, resolveTurnBinding sends no pin validation request and waits for none, whatever the pin env says. A turn's rows name the project's link from the local cache, which is what routing uses then (state.ts, resolveTurnBinding).
  • C2. Every first_prompt_sent and first_job_completed row carries origin. The emit sites are first-events.ts promptSent and jobCompleted, and onboarding-telemetry.ts for the selected job's row. The prompt row is onboarding when the onboarding rule sent it (an onboarding session's first typed prompt, bound or not), and workspace when the workspace rule did. The workspace job row is workspace and the selected-job row is onboarding. No envelope field is named origin, so nothing overwrites it, and these rows' origin overwrites nothing.
  • C3. The workspace rule sends neither row from a turn in a session with a parentID, so a subagent's turn is never the workspace's first prompt or job (prompt.ts). A parent turn whose task call worked is still a job.
  • C4. When a turn passes the scope its binding was resolved under, the first-event mark is keyed by that scope's tenant and API URL, not by the credential read at claim time. Without a scope it reads the current one, as before (first-events.ts claim). A pinned turn's scope is the credential its pin was validated under, carried on the pin path's verdict; it is never read again after the validation (state.ts resolveTurnBinding).
  • C5. A noReply prompt's attachment reads never send a pin validation request or wait for one. In a pinned session (feature on) they name no workspace; otherwise they name the project's link. A prompt with a reply names the turn's verdict on file and directory attachments alike (prompt.ts createUserMessage, state.ts unvalidatedTurnBinding).
  • C6. No loop takes a verdict another prompt resolved. The verdict is cleared when the prompt's message fails to build, when the prompt joins a running loop, on noReply, and at loop start (prompt.ts).
  • C7. A mark that can't be written claims nothing, and the next turn tries again. The warning is logged once per process.

Disclosed residuals

  • R1. Overlapping prompts in one session share one verdict slot, keyed by session as since fix: a pinned session's telemetry names the picked workspace #1672. A prompt that joins a running loop resolves its own verdict for its attachments, while the loop's rows keep the verdict from the loop's start. A prompt that ends or starts can clear a verdict another prompt resolved, and that prompt's loop then resolves again. If validation answered differently in between, one turn's rows can name two answers.
  • R2. Memory sync asks the account's workspace list on its own, to see whether memory is on. A pinned turn can send that request beside the pin's validation. This predates this PR.
  • R3. The pin's success line logs at debug when the memo answered, so a process logs it at info only when it validates.
  • R4. On Windows the mark's file mode has no effect; it takes the state directory's permissions.
  • R5. The mark records a claim, not a delivery. A process killed between the two loses the row rather than duplicating it (documented).
  • R6. C6's clears and C7's once-only warning have no test of their own. The routes that would take a stale verdict are a loop started without prompt() (summarize, resume).
  • R7. Run alone, the loop test files report an unhandled "All fibers interrupted" error between tests. main's prompt.test.ts and snapshot-tool-race.test.ts do the same; CI's one-process run doesn't.
  • R8. The onboarding hook's mark, for the selected job's row in onboarding-telemetry.ts, is claimed with the credential read at claim time, as before this PR. A credential switch inside that turn can key it under the new tenant, so the workspace's own row can be sent as well: one duplicate row.

Rounds

  • Round 1 (scoped to C1–C7), at 1341f81c1e: no claim violation. Codex: "Didn't find any major issues", with no inline findings.
  • After the ready flip, at 1341f81c1e: cubic 7, Kilo 2, CodeRabbit none. Fixed in 0909886de0: a pinned turn's scope is read again after validation, raised by cubic and Kilo (C4, now with a test); the origin wording, raised by Kilo (C2); the reset comment; the docs note; a timing check. Declined: the session-keyed clear (R1) and the onboarding hook's claim-time credential (R8, raised twice).
  • Round 2 (scoped to C2 and C4), at 0909886de0: no claim violation. On the same head cubic found 0 issues and Kilo found none.

Appendix — complexity delta

4f63b2b020 → 0909886de0 · only functions this diff touches · advisory, not a gate. Test files excluded.

Function File Cognitive CCN
resolvePinnedBinding workspace/state.ts 21 → 22 18 → 19
claim workspace/first-events.ts 7 → 8 8 → 10
promptSent workspace/first-events.ts 3 → 5 5 → 6
resolveTurnBinding workspace/state.ts 3 → 4 4 → 5
parts.map callback in createUserMessage session/prompt.ts 103 → 103 35 → 35

17 touched · 4 rose · 3 new (max cognitive 2) · net cognitive +5. The tool matches functions by line, so it paired the createUserMessage callback with the new attachmentWorkspace closure six lines above it. It reported "0 → 103" and "103 → 1"; the callback is 103 on both sides. Nothing this PR adds crosses 15. resolvePinnedBinding was already over 15, and only its log line changed.

@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review against the numbered claims and the disclosed residuals in the review-log comment on this PR (#1705 (comment)): report only a reproducible trace that violates a numbered claim. Instances of the disclosed residuals are not findings. A round with no claim violation ends review.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-10T11:53:39.209030Z 0909886 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 1341f81c1e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ralphstodomingo
ralphstodomingo marked this pull request as ready for review November 10, 2026 11:27

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/altimate/workspace/first-events.ts">

<violation number="1" location="packages/opencode/src/altimate/workspace/first-events.ts:49">
P2: `claim` falls back to ambient credentials when no scope is passed, but the onboarding hook supplies only `workspaceId`. A credential switch during the turn can mark tenant B for tenant A, after which `jobCompleted` emits a duplicate or credits the wrong tenant; pass the turn's resolved scope to this claim.</violation>
</file>

<file name="packages/opencode/src/altimate/workspace/state.ts">

<violation number="1" location="packages/opencode/src/altimate/workspace/state.ts:501">
P2: `__resetPinValidation` does not actually isolate an in-flight validation: the old `resolvePinnedBinding` continuation can still write `pinValidation` after this reset. Invalidate requests with a generation/token and ignore completions from before the reset, otherwise later tests can inherit stale pin verdicts.</violation>
</file>

<file name="packages/opencode/src/session/prompt.ts">

<violation number="1" location="packages/opencode/src/session/prompt.ts:565">
P2: `endTurnBinding` can clear a newer prompt's verdict because it is keyed only by `sessionID`. Concurrent prompts can make the later turn resolve a different workspace after its attachment already used the first; make cleanup compare against this prompt's own verdict.</violation>
</file>

<file name="packages/opencode/src/altimate/plugin/onboarding-telemetry.ts">

<violation number="1" location="packages/opencode/src/altimate/plugin/onboarding-telemetry.ts:269">
P2: `origin: "onboarding"` identifies the turn's workspace, but the suppression claim below still uses the ambient credential instead of that turn's resolved scope. Pass the resolved binding scope through the tool hook before claiming, or an account switch can mis-key the mark and produce a duplicate workspace row.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

const scope = await currentScope().catch(() => null)
// The credential the turn's binding was resolved under, when the caller has it: the workspace id
// belongs to that tenant, and the turn doesn't read the credentials again.
scope ??= (await currentScope().catch(() => null)) ?? undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: claim falls back to ambient credentials when no scope is passed, but the onboarding hook supplies only workspaceId. A credential switch during the turn can mark tenant B for tenant A, after which jobCompleted emits a duplicate or credits the wrong tenant; pass the turn's resolved scope to this claim.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/altimate/workspace/first-events.ts, line 49:

<comment>`claim` falls back to ambient credentials when no scope is passed, but the onboarding hook supplies only `workspaceId`. A credential switch during the turn can mark tenant B for tenant A, after which `jobCompleted` emits a duplicate or credits the wrong tenant; pass the turn's resolved scope to this claim.</comment>

<file context>
@@ -38,9 +42,11 @@ const sent = new Set<string>()
-  const scope = await currentScope().catch(() => null)
+  // The credential the turn's binding was resolved under, when the caller has it: the workspace id
+  // belongs to that tenant, and the turn doesn't read the credentials again.
+  scope ??= (await currentScope().catch(() => null)) ?? undefined
   if (!scope) return false
   const id = createHash("sha256")
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this in this PR. It's R8 on the review log. The onboarding hook claims its mark with the credential read at claim time, as it did before this PR; C4 covers only a passed scope. Passing the turn's scope here means adding it to the tool context the plugin hook receives, which is wider than this follow-up. The effect needs a credential switch inside an onboarding turn, and it is at most one duplicate workspace row.

export function __resetPinValidation(clock?: () => number): void {
pinValidation.clear()
// A request an earlier test started could otherwise settle after the reset and fill the memo.
pinValidationInFlight.clear()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: __resetPinValidation does not actually isolate an in-flight validation: the old resolvePinnedBinding continuation can still write pinValidation after this reset. Invalidate requests with a generation/token and ignore completions from before the reset, otherwise later tests can inherit stale pin verdicts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/altimate/workspace/state.ts, line 501:

<comment>`__resetPinValidation` does not actually isolate an in-flight validation: the old `resolvePinnedBinding` continuation can still write `pinValidation` after this reset. Invalidate requests with a generation/token and ignore completions from before the reset, otherwise later tests can inherit stale pin verdicts.</comment>

<file context>
@@ -496,6 +497,8 @@ let pinNow: () => number = () => Date.now()
 export function __resetPinValidation(clock?: () => number): void {
   pinValidation.clear()
+  // A request an earlier test started could otherwise settle after the reset and fill the memo.
+  pinValidationInFlight.clear()
   pinNow = clock ?? (() => Date.now())
 }
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partly right. The reset stops a later caller from joining a request started before it, but a caller already awaiting that request still writes the memo when it answers, so the comment overstated it. Fixed in 0909886 to say so. No code change: the tests that start a slow validation await their turn, and the turn's routing read awaits that request, so none outlives its test. A generation counter would add production state only for the test seam.

const message = await createUserMessage(input)
// altimate_change — a prompt that fails here runs no loop, so no later loop may take its verdict
const message = await createUserMessage(input).catch((err) => {
endTurnBinding(input.sessionID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: endTurnBinding can clear a newer prompt's verdict because it is keyed only by sessionID. Concurrent prompts can make the later turn resolve a different workspace after its attachment already used the first; make cleanup compare against this prompt's own verdict.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/session/prompt.ts, line 565:

<comment>`endTurnBinding` can clear a newer prompt's verdict because it is keyed only by `sessionID`. Concurrent prompts can make the later turn resolve a different workspace after its attachment already used the first; make cleanup compare against this prompt's own verdict.</comment>

<file context>
@@ -560,7 +560,11 @@ export namespace SessionPrompt {
-    const message = await createUserMessage(input)
+    // altimate_change — a prompt that fails here runs no loop, so no later loop may take its verdict
+    const message = await createUserMessage(input).catch((err) => {
+      endTurnBinding(input.sessionID)
+      throw err
+    })
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this. It's R1 on the review log, now worded for any overlapping prompts. The slot has been keyed by session since #1672, including the clear at the start of prompt(). A cleared verdict makes a loop resolve again; it never takes another prompt's verdict, which is C6. Getting two answers takes a second prompt in the same session while the first is still building its message, and a validation answer that changes in between.

// names its own turn's workspace, not the replacement's.
void OnboardingTelemetry.emit(
{ type: "first_job_completed", job, workspace_id: input.workspaceId },
{ type: "first_job_completed", job, workspace_id: input.workspaceId, origin: "onboarding" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: origin: "onboarding" identifies the turn's workspace, but the suppression claim below still uses the ambient credential instead of that turn's resolved scope. Pass the resolved binding scope through the tool hook before claiming, or an account switch can mis-key the mark and produce a duplicate workspace row.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/opencode/src/altimate/plugin/onboarding-telemetry.ts, line 269:

<comment>`origin: "onboarding"` identifies the turn's workspace, but the suppression claim below still uses the ambient credential instead of that turn's resolved scope. Pass the resolved binding scope through the tool hook before claiming, or an account switch can mis-key the mark and produce a duplicate workspace row.</comment>

<file context>
@@ -266,7 +266,7 @@ export async function OnboardingTelemetryPlugin(_input: PluginInput): Promise<Ho
         // names its own turn's workspace, not the replacement's.
         void OnboardingTelemetry.emit(
-          { type: "first_job_completed", job, workspace_id: input.workspaceId },
+          { type: "first_job_completed", job, workspace_id: input.workspaceId, origin: "onboarding" },
           input.sessionID,
         )
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same point as the first-events.ts thread: R8 on the review log. This row's mark is claimed with the credential read at claim time, as before this PR. Carrying the turn's scope into the hook needs it on the tool context, which is out of scope here. The effect is at most one duplicate workspace row after a credential switch inside an onboarding turn.

Comment thread docs/docs/reference/telemetry.md Resolved
if (pinned) return pinned.status === "bound" ? pinned.binding : null
if (pinned) {
if (pinned.status !== "bound") return null
return { ...pinned.binding, scope: (await accountKey()) ?? undefined }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: The pinned arm's scope is not "the credential the binding was resolved under" — it is a second, ambient credential read taken after the async validation.

resolvePinnedBinding already resolved credentials (line 634) and used them for both the cache key and the listDatamates request, which is exactly what its own comment at ~line 624 says to do instead of also calling accountKey(). Calling accountKey() here duplicates that credential load on every pinned turn, and it re-opens the A→B window that function went to lengths to close: if the credential switches tenant/host while the pin validation is in flight, this pairs a workspace id validated under tenant A with a scope from tenant B. first-events.claim keys the mark on tenant + apiUrl + workspaceId + event, so that mismatch files the claim under the wrong tenant — suppressing a genuine first_prompt_sent/first_job_completed for B's workspace with the same id, or crediting A's row to B.

The linked arm (linkedTurnBinding) is atomic — one accountKey() read paired with the binding — so the two arms disagree on the invariant the new TurnBinding comment states. Consider returning the credential (or just tenant/apiUrl) from resolvePinnedBinding alongside the outcome instead of re-reading it here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0909886. This is the same finding cubic raised on this line. resolvePinnedBinding now returns the credential it validated under as scope on the bound verdict, and resolveTurnBinding uses it, so no credentials are read after the validation and both arms pair the binding with the credential that resolved it. A new test switches the credential mid-validation and checks the scope stays on the validating tenant. It fails without the fix.

Comment thread docs/docs/reference/telemetry.md Outdated
| `activation_job_selected` / `first_job_completed` | Which activation job the user started and, where observable, finished. In onboarding, completion is reported only for the job that was actually selected, so the two form a coherent pair. `first_job_completed` is also sent once per workspace for each machine and Altimate instance, for the first turn in a linked project that had a tool call that didn't fail and finished. **Derived** — see the note below. |
| `first_prompt_sent` | The user's first typed message in an onboarding session, and the first typed message in a linked project, once per workspace for each machine and Altimate instance. Slash commands are excluded, so the hidden `/onboard-connect` submission does not count. |
| `activation_job_selected` / `first_job_completed` | Which activation job the user started and, where observable, finished. In onboarding, completion is reported only for the job that was actually selected, so the two form a coherent pair. `first_job_completed` is also sent once per workspace for each machine and Altimate instance, for the first turn in a linked project, or in a session pinned to a workspace by the IDE extension, that had a tool call that didn't fail and ended `completed`. `origin` says which: `onboarding` or `workspace`. **Derived** — see the note below. |
| `first_prompt_sent` | The user's first typed message in an onboarding session, and the first typed message in a linked project or a session pinned to a workspace, once per workspace for each machine and Altimate instance. A slash command sent as a command, as the TUI and `run` send it, is excluded, so the hidden `/onboard-connect` submission does not count. The VS Code chat sends a command as its expanded text, so there a command counts. A subagent's prompt never counts. `origin` is `onboarding` for an onboarding session's row, bound or not, and `workspace` otherwise. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: This over-states origin. In first-events.ts promptSent, origin is onboarding only when isOnboardingSession(sessionID) && claimFirstPrompt(sessionID) is true — i.e. when the onboarding rule sent the row — not whenever the session is an onboarding one.

A reachable case where the two differ: an onboarding session in an unbound project sends its row on turn 1 (onboarding claim consumed, no workspace claim because workspaceId was undefined); the project is then linked mid-session; on turn 2 the onboarding claim is false and the workspace claim succeeds, so the row is emitted with origin: "workspace" even though the session is an onboarding one. The same wording is repeated in the origin comment on the first_prompt_sent variant in telemetry/index.ts (~lines 1560-1561), so both should say "whichever rule sent the row" rather than keying on the session type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, fixed in 0909886. The docs row, the note and the origin comment on the first_prompt_sent type now say which rule sent the row: onboarding for an onboarding session's first typed prompt, workspace for the workspace's row. The docs row also gives your case: a later prompt, once the project is linked, is a workspace row. C2 on the review log is reworded the same way.

@kilo-code-bot

kilo-code-bot Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Incremental review of 1341f81c1e..0909886de0 (4 files, 27+/10-).

The two earlier findings are resolved:

  • packages/opencode/src/altimate/workspace/state.ts:803 — the pin's bound verdict now carries the credential it was validated under (scope set from the creds read before listDatamates, at state.ts:732), and resolveTurnBinding uses it instead of re-reading credentials. That scope uses the exact same field mapping as accountKey() (tenant/apiUrl/account), so pin and link claims key identically; the added test switches the credential mid-validation and would fail without the fix.
  • docs/docs/reference/telemetry.md:74 — origin is now described as the rule that sent the row, matching first-events.ts (onboarding claim vs. workspace claim), with the linked-mid-session case spelled out; the origin comment in telemetry/index.ts matches.

Also verified: the cleaned-up __resetPinValidation comment now states its actual guarantee, and the new test's getCredentials/listDatamates stubs are reset by beforeEach/afterEach, so no cross-test leak. No cost side effects; the diff removes a credential read on the pinned turn path.

Files Reviewed (4 files)
  • docs/docs/reference/telemetry.md - no issues
  • packages/opencode/src/altimate/telemetry/index.ts - no issues
  • packages/opencode/src/altimate/workspace/state.ts - no issues
  • packages/opencode/test/altimate/workspace/routing-pin.test.ts - no issues
Previous Review Summary (commit 1341f81)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 1341f81)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/altimate/workspace/state.ts 803 The pinned arm's scope is a second ambient credential read taken after the async validation, not "the credential the binding was resolved under". It duplicates the credential load resolvePinnedBinding already did (and its comment says to avoid), and a tenant/host switch during validation pairs a workspace id validated under A with a scope from B — first-events.claim then keys the mark under the wrong tenant.

SUGGESTION

File Line Issue
docs/docs/reference/telemetry.md 74 Over-states origin for first_prompt_sent: the code sets it from the onboarding claim, not from the session type, so a later prompt in an already-claimed onboarding session that first claims the workspace row is labelled workspace. Same wording in the origin comment in telemetry/index.ts.
Files Reviewed (11 files)
  • docs/docs/reference/telemetry.md - 1 issue (doc accuracy)
  • packages/opencode/src/altimate/plugin/onboarding-telemetry.ts - clean
  • packages/opencode/src/altimate/telemetry/index.ts - 1 issue (comment accuracy, same finding as above)
  • packages/opencode/src/altimate/workspace/first-events.ts - clean
  • packages/opencode/src/altimate/workspace/state.ts - 1 issue
  • packages/opencode/src/session/prompt.ts - clean
  • packages/opencode/test/altimate/telemetry/onboarding.test.ts - clean
  • packages/opencode/test/altimate/workspace/first-events.test.ts - clean
  • packages/opencode/test/altimate/workspace/routing-pin.test.ts - clean
  • packages/opencode/test/session/pinned-workspace-loop.test.ts - clean
  • packages/opencode/test/session/workspace-first-events-loop.test.ts - clean

Reviewed at 1341f81c1e. Kill-switch skip, origin on both first events, the subagent guard, the noReply attachment path, the per-turn verdict clears and the once-only warning all read correctly against their stated claims; no cost side effects (the kill switch removes one request). No pre-existing inline comments were present, and no duplicate was filed.

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash-1017 · Input: 91.1K · Output: 20K · Cached: 1.5M

Review guidance: REVIEW.md from base branch main (truncated)

The pin's verdict now carries the credential it was validated under, so a switch during validation can't key the first-event mark under another tenant. Also: `origin` is described by the rule that sent the row, and a flaky timing check is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VEmKQswEEpkYFmMEngESx8
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review against the numbered claims and the disclosed residuals in the review-log comment on this PR (#1705 (comment)). Scoped round on 0909886de0, the post-flip fixes: C4 (a pinned turn's scope is the credential its pin was validated under, never read again after the validation) and C2 (origin names the rule that sent the row). Report only a reproducible trace that violates C2 or C4. Instances of the disclosed residuals (R1, R8 among them) are not findings. A round with no claim violation ends review.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 0909886de0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dev-punia-altimate

Copy link
Copy Markdown
Contributor

🤖 Code Review — OpenCodeReview (Gemini) — No Issues Found

No supported files changed.

This branch has not been deployed

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

Projects

None yet

2 participants