Skip to content

fix(workspace): open the link pickers on a workspace that already has the project's name - #1379

Merged
ralphstodomingo merged 12 commits into
mainfrom
fix/link-picker-same-name
Oct 1, 2026
Merged

ralphstodomingo merged 12 commits into
mainfrom
fix/link-picker-same-name

Conversation

@ralphstodomingo

@ralphstodomingo ralphstodomingo commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #1378

Type of change

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

What does this PR do?

With no workspace linked, every place that offers a quick create (the altimate-code link CLI, the TUI's on-demand picker, and the setup dialog shown after a project scan) made a second workspace with the project's name on a plain Enter, even when one already existed.

  • Matching. One shared helper finds the listed workspaces with the project's name. It ignores case, whitespace and control characters, but not accents, through a Unicode collator pinned to en, so the host locale can't change the result. "Straße" matches "STRASSE"; "café" and "cafe", or dotless and dotted i, stay different.
  • What Enter does. A picker opens on a same-named workspace only if you own it. A colleague's is listed as "same name, owned by someone else" but not preselected, because linking sends this machine's memory to the workspace. An existing link still comes first.
  • Creating anyway. Every create path (quick create, or browser setup where offered) asks first when the name is taken. The default answer is No, and declining changes nothing.
  • The setup dialog offers your own same-named workspace as its first, default choice.

Not covered: when the workspace list can't be read, there is nothing to compare against, and create works as before.

How did you verify your code works?

  • Matrix tests for the helper: matching (case, sharp S, final sigma, ligatures, NFC and NFD, zero-width and bidi characters, dotless i, accents, Danish aa), which namesake may be preselected, the row labels, where a picker opens, and which choices confirm.

  • Wiring tests render the setup dialog and the on-demand picker against a stand-in select and drive their callbacks:

    • both create actions ask first;
    • No makes no create;
    • Yes creates exactly once;
    • your own namesake is preselected, a colleague's is not.

    Each test fails when its gate is removed.

  • Typecheck is clean. The workspace, plugin and CLI link suites pass.

  • The CLI and the TUI picker were driven in a terminal against a local stand-in workspace API. The setup dialog opens only after an agent's project scan, so it was covered by the wiring tests rather than driven.

Screenshots / recordings

An unlinked "analytics" repo. Before (main): the picker opens on create, and Enter makes a second "analytics".

before

After, with only a colleague's "analytics": it is labelled, not preselected.

cli colleague

Enter on create asks first, with No as the default, and No changes nothing.

cli confirm

cli declined

After, with your own "analytics": the picker opens on it.

cli mine

The TUI picker, in the same two cases, and its confirmation:

tui colleague

tui confirm

tui mine

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD

… the project's name

When no workspace is linked, both link pickers opened on "create a quick
workspace", even when the list showed a workspace with exactly the name the
create would use. Enter then made a second workspace with that name, splitting
the team's skills and memory between the two.

- `sameNamedWorkspace()` (workspace-name.ts) finds a listed workspace whose
  name matches the proposed one, comparing case- and whitespace-insensitively.
- `altimate-code link` and the TUI's on-demand picker open on that workspace
  and mark it "same name as this project".
- Creating a workspace with a name that is already listed asks for
  confirmation first (defaulting to no in the CLI).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
@ralphstodomingo ralphstodomingo self-assigned this Sep 28, 2026
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: c517d108-fd29-49bc-8f3f-46aa13274acc

📥 Commits

Reviewing files that changed from the base of the PR and between 77716eb and c7c18d2.

📒 Files selected for processing (6)
  • packages/opencode/src/altimate/workspace/workspace-name.ts
  • packages/opencode/src/cli/cmd/link.ts
  • packages/opencode/src/plugin/tui/altimate/workspace.tsx
  • packages/opencode/test/altimate/plugin/workspace.test.ts
  • packages/opencode/test/altimate/workspace/same-named-workspace.test.ts
  • packages/opencode/test/cli/cmd/link.test.ts

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


📝 Walkthrough

Walkthrough

The CLI and TUI workspace pickers detect listed workspaces whose names match the project name. When applicable, they select and label a matching workspace. They ask for confirmation before creating another workspace with that name.

Changes

Namesake Workspace Selection

Layer / File(s) Summary
Workspace name matching and tests
packages/opencode/src/altimate/workspace/workspace-name.ts, packages/opencode/test/altimate/workspace/same-named-workspace.test.ts
Adds normalized name matching, ownership detection, picker selection, and namesake hints. Matching ignores case and selected Unicode and whitespace differences while preserving accent distinctions. Display formatting strips bidi controls.
CLI picker namesake handling
packages/opencode/src/cli/cmd/link.ts, packages/opencode/test/cli/cmd/link.test.ts, packages/opencode/test/skill/release-v0.11.2-adversarial.test.ts
The CLI picker shows namesake hints and selects an applicable workspace by default. It asks for confirmation before duplicate creation. The sanitizer delegates bidi-control removal to the shared helper, and the control-character test includes U+061C.
TUI picker namesake handling
packages/opencode/src/plugin/tui/altimate/workspace.tsx, packages/opencode/test/altimate/plugin/workspace.test.ts
Setup and on-demand pickers prioritize the caller’s matching workspace, show namesake hints, and confirm before duplicate creation. Tests cover linking, creation, and declining confirmation.

Priority: ➖ Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant Picker as CLI or TUI picker
  participant Matcher as Workspace name utilities
  participant User
  participant Creation as Workspace creation
  Picker->>Matcher: Find namesakes for the proposed name
  Matcher-->>Picker: Return matching workspaces and caller-owned match
  Picker->>User: Show picker with namesake selection and hints
  User->>Picker: Choose a create option
  Picker->>User: Request confirmation when namesakes exist
  User->>Picker: Confirm creation
  Picker->>Creation: Create workspace
Loading

Suggested reviewers: sahrizvi

Merge Risk: ⚪ Minimal · up to c7c18

The changes favor caller-owned matching workspaces and ask before duplicate creation. No actionable merge-blocking risk remains; normal checks should precede merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to c7c18

The change improves duplicate prevention and avoids defaulting to someone else's workspace during a stable sign-in. However, a pending namesake selection can outlive an account or tenant change, creating a conditional risk of linking the project to a different workspace than the one displayed.

Retained concerns

  • Medium · security · inferred: The new namesake default does not carry the discovering tenant and principal through submission. Workspace listing, identity lookup, and binding each resolve ambient credentials independently. If credentials change while the dialog is pending, the selected ID can be submitted under a different account than the one that supplied its name and ownership. Existing manual pickers had this underlying condition, but the PR adds a direct default action that inherits it. Wrong-workspace binding is conditional on the later account accepting that ID; an authorization bypass is not established.
Security review details

Security Blast Radius

  • inferred — The identified concern affects the selected project's workspace binding under the credentials active at submission, with potential downstream effects on workspace-associated memory routing. It requires an account or tenant transition and an accepted target ID; a remotely supplied namesake alone does not demonstrate cross-tenant access or privilege escalation.

Security Findings and Attack Paths

  • inferred — A conditional path is discovery under tenant A, a credential switch while the namesake dialog remains open, and submission of A's numeric workspace ID under tenant B. If B accepts that number as another workspace, ordinary authorization can succeed without preserving the identity displayed to the user. The new default exposes this inherited stale-selection condition without another picker step; the backend outcome remains unverified.

Trust Boundaries and Controls

  • observed — Workspace requests send a bearer credential and tenant header. The UI handles forbidden binding responses rather than bypassing them. Existing browser handoff checks tenant and API URL continuity, but the new namesake action does not carry an equivalent discovery-account check into binding.

Resilience and Maintainability Implications

  • observed — Declining or cancelling duplicate confirmation returns before creation or binding. Existing-link selection remains a no-op. Rebinding retains expected-current-workspace preconditions, and the TUI reports concurrent-link and authorization failures rather than treating them as successful attachment.

Hardening Proposals

  • proposed — Bind workspace discovery, caller identity, submission, and approval bookkeeping to one explicit credential context. Invalidate and refresh pending selections when that context changes, rather than relying on numeric IDs or separate before-and-after credential comparisons.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.87% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #1378 requires the CLI and TUI pickers to open on a listed workspace that matches the project name. It also requires confirmation before creating another workspace with that name. The shared wor…
Out of Scope Changes check ✅ Passed The changed source files implement namesake matching, picker selection, display labels, and duplicate-creation confirmation for issue #1378. The added tests validate these behaviors. The control-chara…
Title check ✅ Passed The title clearly states the primary change: link pickers now open on an existing workspace that matches the project name.
Description check ✅ Passed The description includes the issue, change type, implementation details, verification steps, screenshots, and checklist. It explains namesake matching, picker behavior, duplicate-creation confirmation…
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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 names by the soft moonlight
“This one is mine,” it hops with delight
A duplicate waits for a yes
Bidi marks leave the names in less mess
The picker now knows where to begin
And safely lets a new workspace in

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

Please try to falsify these claims with concrete input:

  1. With no current link, both pickers (altimate-code link and the TUI on-demand picker) open on the first listed workspace whose name matches the one a quick create would use, ignoring case, whitespace and control characters. An existing link still takes precedence.
  2. Choosing create while a same-named workspace is listed always asks for confirmation first, and declining or cancelling changes nothing.
  3. Nothing else about the pickers changes: options, order, and the browser and create flows.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-01T01:30:16.196478Z cb26ed1 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 chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a3d3e92e0

ℹ️ 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".

.replace(/[\u0000-\u001F\u007F-\u009F\u2028\u2029]+/g, " ")
.replace(/\s+/g, " ")
.trim()
.toLowerCase()

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 Badge Use Unicode case folding for workspace names

For an unlinked project named STRASSE with a listed workspace named Straße, sameNamedWorkspace([{ id: 1, name: "Straße" }], "STRASSE") returns undefined: lowercasing produces straße versus strasse, although these are case variants under Unicode caseless matching. Because remote and directory names may contain arbitrary Unicode, both pickers then remain on Create and skip the duplicate-name confirmation, contrary to the claimed case-insensitive behavior; use Unicode case folding rather than toLowerCase().

Useful? React with 👍 / 👎.

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.

Confirmed. Fixed in 1788d02: the comparison now upper-cases before lower-casing, which gives full case folding, so Straße and STRASSE compare equal. Test case added: "case differs beyond ASCII (full case folding)".

Lower-casing alone leaves `Straße` and `STRASSE` different; upper-casing first
gives full case folding, so a namesake that differs only by case is found.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

Scope: only 1788d02, the case-folding fix for your finding. Please try to falsify claim 1 ("ignoring case, whitespace and control characters") with concrete names.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1788d02ee3

ℹ️ 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".

Comment on lines +59 to +60
.toUpperCase()
.toLowerCase()

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 Badge Handle capital sharp S in full case folding

Fresh evidence against this fix is the concrete pair ẞ and SS: the new sequence normalizes them to ß and ss, respectively, so sameNamedWorkspace([{ id: 1, name: "ẞ" }], "SS") still returns undefined even though Unicode full case folding maps both names to ss. Thus a project differing only by this case variant can still bypass the duplicate-name confirmation; use an actual full case-fold implementation rather than composing locale-neutral upper- and lower-casing.

Useful? React with 👍 / 👎.

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.

Confirmed; fixed in the next commit: the comparison now lower-cases, upper-cases, then lower-cases again, so ẞ and SS both reach ss. Straße / STRASSE still match, and both pairs are in the test matrix. That is Unicode case mapping rather than the complete CaseFolding table, which JavaScript does not provide. I have recorded that as a residual in the description rather than hand-maintaining the table for workspace names.

…hes SS

An upper-then-lower pass leaves `ẞ` as `ß` while `SS` becomes `ss`; lower-casing
first lets the expanding mappings meet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

A fresh round on the current head. Please try to falsify the three claims from the earlier summon (the namesake default, the confirmation before a duplicate, and nothing else about the pickers changing), plus the residual about Unicode case mapping. Report only what concrete input shows false.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8fc43d3a15

ℹ️ 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".

Comment thread packages/opencode/src/cli/cmd/link.ts Outdated
Comment on lines +347 to +348
if (pick === CREATE_NEW_SENTINEL) {
if (namesake) {

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 Badge Confirm namesakes created through the browser path

For an unlinked freemium project named analytics with an existing analytics workspace, selecting “Set up in browser” still calls runBrowserHandoff(identifier, autoName, ...) and creates another workspace without reaching this confirmation, because the namesake check is confined to the quick-create sentinel branch. Apply the duplicate-name confirmation to every creation option, including browser setup.

Useful? React with 👍 / 👎.

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.

Confirmed; fixed in 0ebb311. The namesake confirmation now runs before either create path, "Set up in browser" included, since both start from the project's name.

Comment on lines +61 to +63
.toLowerCase()
.toUpperCase()
.toLowerCase()

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 Badge Keep dotless I distinct during Unicode case folding

Fresh input against the Unicode fix is ı versus i: Unicode default case folding keeps dotless ı distinct, but this lower/upper/lower sequence maps both strings to i. Consequently, sameNamedWorkspace([{ id: 1, name: "ı" }], "i") returns that workspace, causing both pickers to default to an unrelated workspace and warn about a duplicate that does not exist.

Useful? React with 👍 / 👎.

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.

Confirmed; fixed in 0ebb311 by switching from chained case mapping to a Unicode collator that ignores case but not accents, with ß spelled out first. ı/i and café/cafe now stay distinct, while Straße/STRASSE, ẞ/SS, ΟΔΟΣ/οδος and finance/FINANCE still match. All of these are in the test matrix.

…esakes on the browser path too

- `sameNamedWorkspace` compares names with a case-insensitive, accent-sensitive
  Unicode collator (after spelling out ß), instead of chained case mapping that
  merged dotless ı with i. `Straße`/`STRASSE`, `ẞ`/`SS`, `ΟΔΟΣ`/`οδος` and
  `finance`/`FINANCE` match; `ı`/`i` and `café`/`cafe` do not.
- `altimate-code link` asks before either create path when a namesake is
  listed, including "Set up in browser", which also starts from the project's
  name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

Scoped round on 0ebb311, the fix for your two findings in the last round. Please try to falsify these with a concrete input:

  • C1. sameNamedWorkspace treats two names as the same only when they differ in case, whitespace or control characters. Case follows Unicode search collation at accent sensitivity (Intl.Collator("und", { sensitivity: "accent", usage: "search" })), with ß/ẞ spelled ss first. So ı/i and café/cafe stay distinct, while Straße/STRASSE, ẞ/SS, ΟΔΟΣ/οδος and finance/FINANCE match.
  • C2. In altimate-code link, both create paths ("Set up in browser" and "Create a quick workspace") ask for confirmation before creating when a namesake is listed. Declining changes nothing.

Out of scope: the TUI dialog wiring from earlier rounds, which is unchanged. Residual: JavaScript has no complete CaseFolding table, so a collator difference from full Unicode case folding on some other script is accepted.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 0ebb311084

ℹ️ 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 September 29, 2026 05:44

@claude claude 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.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

return
}
props.api.ui.dialog.replace(() => (
<props.api.ui.DialogConfirm

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: Default the duplicate-name confirmation to No

The new confirmation uses DialogConfirm, whose initial store.active is "confirm" (packages/tui/src/ui/dialog-confirm.tsx:23). After selecting Create, one more Enter immediately calls createAndBindInline without the user explicitly choosing Yes. This is especially easy with repeated Enter input and defeats the safety guard this PR adds; use a cancel-first selection here (or make this dialog support an initial cancel choice).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

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 ce6be7c. The duplicate-name question is now a DialogSelect that opens on "No, don't create it". "Yes, create another" has to be chosen, so a stray Enter cannot create the duplicate. DialogConfirm has no cancel-first option, and adding one would change the shared TUI component and the plugin API.

description:
dm.id === props.currentlyLinkedDatamateId
? "currently linked to this project"
: dm.id === namesake()?.id

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: Compute the namesake once per picker update

namesake() scans every listed workspace and constructs a new Intl.Collator, and this expression invokes it for every row rendered by options(). A list of N workspaces therefore triggers N full scans (O(N²) comparisons) on load or rerender, potentially freezing the TUI for large accounts. Resolve the match once outside list.map and reuse its ID for the descriptions and selected value.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

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 ce6be7c. namesake is a createMemo, computed once per list rather than once per row.

@kilo-code-bot

kilo-code-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
packages/opencode/src/plugin/tui/altimate/workspace.tsx 1521 Account drift before namesake discovery skips the list but still offers default creation, bypassing duplicate-name confirmation

The previous finding remains unresolved at the current HEAD. On an account mismatch, namesakesFor returns an empty result; both callers still open OfferDialog, whose default quick-create action proceeds without confirmation and resolves the current account independently. Abort or coherently refresh the flow when its account changes.

Files Reviewed (1 files)
  • packages/opencode/src/plugin/tui/altimate/workspace.tsx - 1 issue (targeted verification of the previous finding)

There are no source changes between 9713c92afe1e2c804d253deaea88dca47decb582 and d546e3614cf2a1caf8d5da1de110fb316086b982; the latest commit only reruns CI. No new findings or duplicate inline comments were posted. Tests were not run in read-only review mode.

Fix these issues in Kilo Cloud

Previous Review Summaries (7 snapshots, latest commit 9713c92)

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

Previous review (commit 9713c92)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
packages/opencode/src/plugin/tui/altimate/workspace.tsx 1521 Account drift before namesake discovery skips the list but still offers default creation, bypassing duplicate-name confirmation
Files Reviewed (5 files)
  • packages/opencode/src/altimate/workspace/api-client.ts - 0 issues
  • packages/opencode/src/altimate/workspace/state.ts - 0 issues
  • packages/opencode/src/cli/cmd/link.ts - 0 issues
  • packages/opencode/src/plugin/tui/altimate/workspace.tsx - 1 issue
  • packages/opencode/test/altimate/plugin/workspace.test.ts - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit cb26ed1)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

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

CRITICAL

File Line Issue
packages/opencode/src/plugin/tui/altimate/workspace.tsx 1196 Previous account-pinning finding remains unresolved: the credential comparison does not pin the subsequent bind/rebind request

The new guard rejects a switch completed before selection, but WorkspaceApi.bindExisting and both rebind methods still call req() without actAs. That helper awaits creds(), which itself awaits isConfigured() before reading credentials again. If credentials switch from A to B during that await, the check at line 1196 has already passed under A and the request submits A's tenant-local workspace ID under B. A successful response has already changed B's server binding before recordApprovedBinding detects the mismatch; the warning then incorrectly says nothing was linked. An A-to-B-to-A transition can also pass the recording comparison. Capture one credential snapshot for discovery and pass it explicitly through the list, identity, and bind/rebind requests rather than relying on separate ambient reads. The added tests switch accounts before selection and stub the bind method, so they do not exercise this request-resolution window. This is the previously reported defect, not an additional finding; no duplicate inline comment was posted.

Files Reviewed (2 files)
  • packages/opencode/src/plugin/tui/altimate/workspace.tsx - 1 issue
  • packages/opencode/test/altimate/plugin/workspace.test.ts - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit c7c18d2)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

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

CRITICAL

File Line Issue
packages/opencode/src/plugin/tui/altimate/workspace.tsx 308 Account switch after namesake discovery can link a colliding ID in another tenant and seed local memory there
Files Reviewed (6 files)
  • packages/opencode/src/altimate/workspace/workspace-name.ts - 0 issues
  • packages/opencode/src/cli/cmd/link.ts - 0 issues
  • packages/opencode/src/plugin/tui/altimate/workspace.tsx - 1 issue
  • packages/opencode/test/altimate/plugin/workspace.test.ts - 0 issues
  • packages/opencode/test/altimate/workspace/same-named-workspace.test.ts - 0 issues
  • packages/opencode/test/cli/cmd/link.test.ts - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit 77716eb)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • packages/opencode/src/cli/cmd/link.ts
  • packages/opencode/test/skill/release-v0.11.2-adversarial.test.ts

Previous review (commit 7ae2ff6)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 files)
  • packages/opencode/src/cli/cmd/link.ts

Previous review (commit ce6be7c)

Status: 1 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
packages/opencode/src/cli/cmd/link.ts 345 New sanitizer drops bidi-control stripping from the duplicate-name confirmation
Files Reviewed (3 files)
  • packages/opencode/src/altimate/workspace/workspace-name.ts - 0 issues
  • packages/opencode/src/cli/cmd/link.ts - 1 issue
  • packages/opencode/src/plugin/tui/altimate/workspace.tsx - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit 0ebb311)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
packages/opencode/src/plugin/tui/altimate/workspace.tsx 1063 Confirmation defaults to Yes, allowing another Enter to create a duplicate
packages/opencode/src/plugin/tui/altimate/workspace.tsx 1027 Per-row namesake scans make picker rendering quadratic
Files Reviewed (4 files)
  • packages/opencode/src/altimate/workspace/workspace-name.ts - 0 issues
  • packages/opencode/src/cli/cmd/link.ts - 0 issues
  • packages/opencode/src/plugin/tui/altimate/workspace.tsx - 2 issues
  • packages/opencode/test/altimate/workspace/same-named-workspace.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by gpt-sol-latest · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@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.

All reported issues were addressed across 4 files

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

Re-trigger cubic

Comment thread packages/opencode/src/plugin/tui/altimate/workspace.tsx Outdated
Comment thread packages/opencode/src/cli/cmd/link.ts Outdated
Comment thread packages/opencode/src/altimate/workspace/workspace-name.ts Outdated
Comment thread packages/opencode/src/plugin/tui/altimate/workspace.tsx Outdated
…once per list

The TUI question is a select that opens on No, so a stray Enter cannot create
the duplicate. The namesake is computed once per list, and naming and
comparing share one normaliser.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
@ralphstodomingo
ralphstodomingo force-pushed the fix/link-picker-same-name branch from 778a630 to ce6be7c Compare September 30, 2026 00:38
Comment thread packages/opencode/src/cli/cmd/link.ts Outdated
// Both create paths start from the project's name, so both confirm a namesake.
if ((pick === SET_UP_IN_BROWSER_SENTINEL || pick === CREATE_NEW_SENTINEL) && namesake) {
const again = await prompts.confirm({
message: `A workspace named "${inertWorkspaceName(namesake.name)}" already exists. Create another one with the same name?`,

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: Preserve bidi-control stripping in the confirmation

Replacing stripControlChars with inertWorkspaceName drops the existing removal of U+200E/U+200F, U+202A–U+202E and U+2066–U+2069. The latter helper only replaces C0/C1 controls and line separators, so a server-supplied namesake containing a right-to-left override (\u202e) now reaches prompts.confirm intact and can visually reorder the name or following confirmation text in bidi-aware terminals. The picker still strips these characters, making the same workspace display differently at the confirmation boundary. Compose the existing sanitizer with the new one to retain both protections.

Suggested change
message: `A workspace named "${inertWorkspaceName(namesake.name)}" already exists. Create another one with the same name?`,
message: `A workspace named "${inertWorkspaceName(stripControlChars(namesake.name))}" already exists. Create another one with the same name?`,

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@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.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/cli/cmd/link.ts Outdated
Comment thread packages/opencode/src/plugin/tui/altimate/workspace.tsx Outdated
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

Scoped round on ce6be7c, the fixes for the bot findings: the TUI same-name question is a select that opens on No, namesake is memoized, the CLI prompt uses inertWorkspaceName, and naming and comparing share one normaliser. Please try to falsify: C1) no single Enter from the picker creates a same-named workspace; C2) sameNamedWorkspace results are unchanged from 0ebb311 for every input.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce6be7c1d5

ℹ️ 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".

Comment thread packages/opencode/src/cli/cmd/link.ts Outdated
// Both create paths start from the project's name, so both confirm a namesake.
if ((pick === SET_UP_IN_BROWSER_SENTINEL || pick === CREATE_NEW_SENTINEL) && namesake) {
const again = await prompts.confirm({
message: `A workspace named "${inertWorkspaceName(namesake.name)}" already exists. Create another one with the same name?`,

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 Badge Keep stripping bidi controls from the CLI prompt

When a server-controlled namesake contains a bidi override such as U+202E, inertWorkspaceName() leaves that character intact, whereas the replaced stripControlChars() call explicitly removed U+202A–U+202E and U+2066–U+2069. The raw Clack confirmation can therefore render visually reordered or misleading text even though every other workspace-name display in this CLI retains the bidi sanitization; preserve stripControlChars() for this terminal-facing interpolation, potentially after the shared formatter.

Useful? React with 👍 / 👎.

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.

Valid, fixed in 7ae2ff6. The prompt now renders stripControlChars(inertWorkspaceName(name)): one line and bounded, then the C0/C1 and bidi controls removed, like every other terminal-facing name in this command.

inertWorkspaceName keeps the name on one line but leaves bidi overrides, which
stripControlChars removes for every other terminal-facing workspace name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

Scoped round on 7ae2ff6: the CLI same-name prompt renders stripControlChars(inertWorkspaceName(name)). Please try to falsify: no server-supplied name can break the prompt onto a new line or reorder it with bidi controls.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ae2ff686e

ℹ️ 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".

Comment thread packages/opencode/src/cli/cmd/link.ts Outdated
// Both create paths start from the project's name, so both confirm a namesake.
if ((pick === SET_UP_IN_BROWSER_SENTINEL || pick === CREATE_NEW_SENTINEL) && namesake) {
const again = await prompts.confirm({
message: `A workspace named "${stripControlChars(inertWorkspaceName(namesake.name))}" already exists. Create another one with the same name?`,

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 Badge Strip the Arabic Letter Mark from the prompt

When a server-supplied name contains U+061C ARABIC LETTER MARK, this composition leaves it intact: inertWorkspaceName() does not treat it as whitespace or a line separator, and stripControlChars() omits it from its bidi-control ranges. U+061C is an invisible Unicode bidi control with bidi class AL, so it can still alter the ordering of adjacent numbers and punctuation in this confirmation prompt; include U+061C in the sanitizer and its exhaustive bidi-control test.

Useful? React with 👍 / 👎.

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.

Valid, fixed in 77716eb. stripControlChars now removes U+061C as well, and the exhaustive bidi scan in release-v0.11.2-adversarial.test.ts includes it. The helper predates this PR, so every workspace name this command prints gets the fix too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD

@sahrizvi sahrizvi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review: PR #1379

Solid fix for the two pickers. One shared helper means the CLI and TUI can't drift. Precedence is existing link, then namesake, then create. The confirmation defaults to No on both surfaces, and the TUI uses DialogSelect rather than DialogConfirm for that reason. DialogSelect also re-runs on current after the async list loads, so the cursor really does move to the namesake. The new test and the adversarial suite pass locally (37 pass, 0 fail).

The one MAJOR finding is inline: the post-scan OfferDialog still creates duplicates. The remaining findings follow.

Minor

1. new Intl.Collator("und", …) follows the host's default locale (workspace-name.ts:78)
"und" isn't a supported collator locale (Intl.Collator.supportedLocalesOf(["und"]) → []), so it resolves to the process default. Under Node with LC_ALL=tr_TR.UTF-8 it resolves to tr-TR, and sameNamedWorkspace([{ name: "ANALYTICS" }], "analytics") returns undefined: Turkish pairs I with ı, not i. Any name containing I/i stops matching, and the user gets the pre-PR behavior back. With a Danish default, "aa" and "å" compare equal, a false-positive namesake. The tests pass only because they run in an en-like locale. Pin it and hoist it, since it is currently rebuilt on every call:

const NAME_COLLATOR = new Intl.Collator("en", { sensitivity: "accent", usage: "search" })

Verified: under a tr_TR host, Intl.Collator("en", …) resolves to en and compares ANALYTICS/analytics as 0. A test asserting resolvedOptions().locale === "en" would pin it.

2. The TUI confirmation shows names less safely than the CLI (workspace.tsx:1067, :1071 vs link.ts:345)
The title uses inertWorkspaceName(twin.name), which leaves bidi controls (ALM, LRM/RLM, LRE..RLO, LRI..PDI) in a server-controlled name. The CLI wraps the same name in stripControlChars(...), the protection this PR's ALM commit exists for. The "Yes, create another …" option interpolates the raw props.defaultName. Moving the bidi strip into workspace-name.ts would let both surfaces share it.

3. The default Enter now links to, and seeds memory into, a namesake that may be a colleague's (workspace.tsx:1039 → bindOrRebindInline → recordApprovedBinding; link.ts:334; workspace-name.ts:79)
This is what #1378 asks for, but it changes what a reflexive Enter does. It used to create a private workspace. Now it links to whichever visible workspace shares the name, which may only be shared with the user ("Linking needs only visibility"), and uploads this machine's memory into it. runFlow avoids exactly that without an explicit Attach (:1257-1262). With two namesakes, server list order decides. Consider preferring a namesake whose ownerId matches the caller, and/or marking ones the caller doesn't own. Not a blocker, but it should be a conscious choice.

4. No tests for the picker or confirmation behavior
The matrix covers only the helper. Nothing asserts any of the following:

  • initial-selection precedence in either picker;
  • that both CLI create sentinels are gated;
  • that No/cancel makes no create or bind call;
  • that the TUI Yes path calls createAndBindInline once.

A refactor could drop the gate and every test would still pass.

5. Contract edges the matrix doesn't pin (same-named-workspace.test.ts)
Worth adding cases for:

  • a control character in the proposed name;
  • NFC vs NFD input ("cafe\u0301" vs "café");
  • zero-width or bidi characters in a listed name. These are collation-ignorable, so "analy\u061Ctics" does match "analytics", which a test should pin as intended;
  • locale independence (see 1).

Nits

  • workspace-name.ts:73: the bare (codex) attribution in a shipped doc comment. Drop it or cite the review round, as other comments in the file do.
  • workspace.tsx:1039: passing namesake()?.id as current makes DialogSelect draw its ● gutter on that row (dialog-select.tsx:585,701-705). In this picker a ● in the title means "currently linked", so on an unlinked project it can read as "linked here". The description text disambiguates.
  • workspace.tsx:1003: "Once per list, not once per row: the options below read it for every workspace" reads as if the per-row read is the cost. Something like "computed once per list; each row reads the cached value" would be clearer. The sameNamedWorkspace doc could also say that ligature and final-sigma equivalence come from collation, not from comparableName.

Comment thread packages/opencode/src/plugin/tui/altimate/workspace.tsx Outdated
…y my own

The post-scan setup dialog created a same-named workspace on Enter. Both of
its create actions now confirm first, and it offers the caller's own
namesake. Pickers preselect only a namesake the caller owns and label a
colleague's. Names compare under a collator pinned to `en`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
Comment thread packages/opencode/src/plugin/tui/altimate/workspace.tsx Outdated
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

Re-review disposition — c7c18d2

MAJOR: the setup dialog's create actions: fixed. Details are on the inline thread.

Minor

  1. Collator follows the host locale: fixed. A single NAME_COLLATOR is pinned to en and hoisted out of the call. A test asserts that it resolves to en. (Under Bun the default locale ignores LC_ALL, so a subprocess test with a Turkish locale would not reproduce the Node behaviour; the pin is what matters.)
  2. TUI shows names less safely than the CLI: fixed. The bidi strip now lives in workspace-name.ts (stripBidiControls, displayWorkspaceName). The TUI confirmation title, its "Yes, create another …" option and the CLI prompt all use it, and the CLI's stripControlChars delegates its bidi part to the same function.
  3. Enter links to a namesake that may be a colleague's: decided with Ralph. A picker opens on a same-named workspace only if the caller owns it, using whoami against the list's ownerId. A colleague's is labelled "same name, owned by someone else" and is not preselected. With only a colleague's namesake, Enter lands on create, which asks first with No as the default. With several namesakes, yours wins. If the owner or the caller is unknown, nothing is preselected.
  4. No tests for the picker or confirmation: added.
    • Pure matrices cover where a picker opens, the row labels, and which choices confirm. In the CLI they cover both create sentinels, through linkPickKind.
    • Wiring tests render OfferDialog and OnDemandPickerDialog against a stand-in DialogSelect and drive onSelect. They pin that No or cancel makes no create call, that Yes creates exactly once, that a free name creates directly, and the owner rules.
    • Each test fails when its gate is removed: 7 mutants were checked.
  5. Contract edges: added to the matrix: a control character in the proposed name, NFC vs NFD, zero-width and bidi characters in a listed name (pinned as matching, on purpose), Danish aa vs å, and the locale pin.

Nits: all addressed.

  • The bare (codex) is gone.
  • The ● gutter no longer lands on a preselected namesake: that row gets a blank gutter.
  • The memo comment is reworded.
  • The helper's doc now says that ligature and final-sigma equivalence come from collation.

Verification

  • Workspace, plugin, CLI link and adversarial suites: 936 pass, 0 fail. Typecheck is clean.
  • The CLI and the TUI on-demand picker were driven against a local stand-in workspace API, once with a colleague's namesake and once with your own. The screenshots are in the description, and no create or bind request reached the stand-in.
  • The setup dialog opens only after an agent's project scan, so it was not driven live. The wiring tests run its real code.

@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.

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/plugin/tui/altimate/workspace.tsx Outdated
…isted it

Workspace ids are per tenant, so an account switch while a picker or the
setup dialog is open could link a different workspace with the same id and
send memory there. A switch since the list loaded now links nothing, and the
record and seed are pinned to that account.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

Round on cb26ed1, covering the changes since 77716eb (setup-dialog gate, owner-aware preselection, account pin). Please try to falsify:

  • C1: No plain Enter, in the CLI picker, the TUI on-demand picker or the post-scan setup dialog, creates a second workspace with the project's name. Every create path asks first, opens on No, and No makes no create call.
  • C2: A picker or the setup dialog preselects a same-named workspace only when the caller owns it (known ownerId equal to whoami). A colleague's is labelled and not preselected. With several namesakes, the caller's wins.
  • C3: Name matching does not depend on the host locale (collator pinned to en). Collation-ignorable characters (zero-width, bidi) do not make names differ.
  • C4: A workspace id is linked only under the account it was listed under. A switch after the list loads links nothing, and the record and memory seed are pinned to that account.
  • C5: Names shown in the TUI confirmation and the CLI prompt carry no bidi controls.

Residual, out of scope: when the workspace list cannot be read, there is nothing to compare against and create proceeds without the confirmation.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb26ed1bee

ℹ️ 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".

Comment on lines +263 to +264
// Listed workspaces already named what a quick create would use. The picker opens on
// the caller's own (never a colleague's), and creating another namesake is confirmed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pin the CLI workspace list and bind to one account

If credentials switch after listDatamates() completes but while the CLI prompt is open, this newly added whoami() runs as the new account and compares its user id with rows from the old account; after selection, bindOrRebind() also captures that new account and can bind the same numeric workspace id there. Because workspace and owner ids are tenant-local, this can preselect an unrelated row and send the project's memory to an unintended workspace instead of linking nothing. Capture one credential before loading the list and use it for the list, ownership lookup, bind, record, and seed.

Useful? React with 👍 / 👎.

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.

Agreed; fixed in 9713c92. The link flows now capture the credential once and pass it to every request (actAs): the CLI before it lists workspaces, the TUI picker on mount, the setup dialog in runFlow (and only when it is still the account the flow started under). The list, whoami, bindExisting and both rebind endpoints (including the CLI's 409 fallback) run as that credential; the record and seed are pinned to its digest. A configured account that differs before the bind still links nothing; one that changes during the bind cannot redirect the request, only stop the local record and seed. Wiring tests assert the list and the bind ran as the captured credential (fail with the bind unpinned).

Comment on lines 1197 to 1200
accountChanged()
return
}
try {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Execute the TUI bind with the captured account

When credentials change after this check resolves but before WorkspaceApi.bindExisting() or the rebind helper resolves its own credentials, the request still executes under the new account because those APIs read ambient credentials independently. The later recordApprovedBinding check can suppress the local record and seed, but it cannot undo the server-side bind that already occurred under another tenant, so the UI may claim nothing was linked even though another account's project was modified. Pass the captured credential through to the bind request rather than relying on a pre-request digest comparison.

Useful? React with 👍 / 👎.

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.

Agreed; fixed in 9713c92. The link flows now capture the credential once and pass it to every request (actAs): the CLI before it lists workspaces, the TUI picker on mount, the setup dialog in runFlow (and only when it is still the account the flow started under). The list, whoami, bindExisting and both rebind endpoints (including the CLI's 409 fallback) run as that credential; the record and seed are pinned to its digest. A configured account that differs before the bind still links nothing; one that changes during the bind cannot redirect the request, only stop the local record and seed. Wiring tests assert the list and the bind ran as the captured credential (fail with the bind unpinned).

…ntial

A digest check before the bind left a window where the request still ran
as whatever account was configured. The CLI and TUI link flows now capture
the credential once and pass it to the list, `whoami`, bind and rebind,
and pin the record and seed to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
): Promise<{ namesakes: Namesakes<DatamateRef>; listedAs: ActAs | null }> {
const none = { namesakes: findNamesakes([] as DatamateRef[], defaultName, undefined), listedAs: null }
const actAs = await WorkspaceApi.captureCredentials()
if (!actAs || digestOf(actAs) !== flowAccount) return none

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: Abort or refresh the offer when its account changes

This new mismatch branch returns an empty namesake result without attempting the list, but both runFlow callers still open OfferDialog. For example, start a post-scan flow under A, switch to B while the pre-check is pending, and let it return unbound. If B already has an analytics workspace and browser handoff is unavailable, this branch discards discovery, the dialog defaults to quick create, and Enter reaches createAndBindInline under B without any duplicate-name confirmation. This introduces a bypass even when B's workspace list is readable; previously namesakesFor would fetch that list and gate creation. On account drift, stop/restart the flow or refresh discovery and the pre-check coherently under the new credential instead of presenting an unchecked create action. Add coverage for a switch before namesake discovery, not only after the offer has loaded.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@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.

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/cli/cmd/link.ts
Comment thread packages/opencode/src/cli/cmd/link.ts
Ralph Sto. Domingo and others added 2 commits October 1, 2026 12:43
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD
Resolves `state.ts` in favour of main's `credentialDigest`; the link flows
now call it directly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172qrhMa5TQgETASi5hxMqD

@sahrizvi sahrizvi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-review at cd23385: approving

Every item from my earlier review is fixed, and I verified each one against the code rather than the disposition comment.

Earlier finding Status
MAJOR: the post-scan OfferDialog created namesakes unchecked Fixed. runFlow passes namesakes in. Both create actions go through createUnlessNamesake and the shared No-default NamesakeConfirmDialog. Your own namesake becomes the first, default choice.
The collator followed the host locale Fixed. NAME_COLLATOR is pinned to en and hoisted, and a test pins resolvedOptions().locale.
The TUI showed names less safely than the CLI Fixed. stripBidiControls and displayWorkspaceName cover the title, the Yes option and the CLI prompt.
The default Enter linked to a colleague's namesake Fixed. The picker opens on your own namesake only (ownerId === whoami). A colleague's row is labelled and not preselected.
No tests for the picker or confirm behavior Fixed. Pure matrices plus wiring tests that render the real dialogs.
Contract edges Fixed. NFC/NFD, ignorable characters, Danish aa/å and the locale pin are all in the matrix.
Nits ((codex), ● gutter, comment wording) Fixed.

Verification on this head:

  • The link, plugin, CLI-link and adversarial suites pass: 147 pass, 0 fail.
  • tsgo --noEmit is clean.
  • I mutation-tested the gates. Each of these makes the suite fail, so the tests prove what they claim:
    • removing the quick-create gate in OfferDialog (2 failures);
    • removing its browser gate (1);
    • reverting the collator to "und" (1);
    • making confirmsNamesake always false (8);
    • ignoring ownerId in findNamesakes (9).

Non-blocking residuals. All of these need the user to switch Altimate accounts within the few seconds a flow is running. Each falls back to the pre-PR behavior or to an existing guard, and none sends data to an account the user didn't choose:

  • kilo's open WARNING (namesakesFor): an account change between the flow's start and the namesake lookup gives an empty namesake set, and the setup dialog offers create without the confirmation. The doc comment on namesakesFor already discloses this. Worth a one-line reply on that thread.

  • The two cubic threads on link.ts show "✅ Addressed in cd23385", but that commit is the merge from main, and the code is unchanged:

    • the pre-check getBindingForProject (:235) still runs on ambient credentials before captureCredentials() (:250);
    • quick create (createThenBindOrRebind, :384) doesn't take actAs.

    Both are covered in practice: the rebind's expectedCurrentDatamateId precondition and the create path's own accountDigest check. Threading actAs through both would make the CLI consistent with the TUI. That can be a follow-up, but the threads should get a reply rather than the bot's auto-resolution.

@ralphstodomingo
ralphstodomingo merged commit a94792a into main Oct 1, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link picker opens on "create" even when a workspace with the project's name is listed

2 participants