Skip to content

chore: sync dev with staging (2026-11-03, project bootstrap) - #9612

Merged
waleedlatif1 merged 18 commits into
devfrom
chore/sync-dev-with-staging-2026-11-03b
Nov 3, 2026
Merged

waleedlatif1 merged 18 commits into
devfrom
chore/sync-dev-with-staging-2026-11-03b

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Brings dev up to current staging, including #9611, which lets dev's push-managed database bootstrap Projects (the previous sync's deploy stopped at the Project migration guard). The merge result is identical to staging.

waleedlatif1 and others added 18 commits November 2, 2026 18:50
* docs(library): update best-zapier-alternatives

* Pi Babysit: address PR #9592 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
…he files it already wrote (#9596)

* improvement(sandbox): return a timed-out workbench run's output and the files it already wrote

A run_code call killed at its own timeout returned an empty stdout and only
"timed out after Nms", so models reran long jobs from scratch. E2B's SDK throws
a stream-less TimeoutError, which dropped the output the stream callbacks had
already observed.

- E2B adapter: a timed-out command returns the stdout/stderr its callbacks observed
- Workbench runs (session sandboxes) that hit their own timeout throw a timeout
  abort carrying the output tail (64 KB) and the files already in the call's
  output directory; every existing timeout check still accepts it
- The route returns that output in stdout with a 408 and says the workbench and
  its files persist, so the next call can rerun only the remaining work
- Output goes through the same session provenance check and secret-name
  reporting as any other result; one-shot sandboxes and billing are unchanged

* fix(sandbox): keep a cancellation during the timed-out output listing a cancellation
…ms-2026 (#9598)

* docs(library): update best-self-hosted-ai-workflow-automation-platforms-2026

* Pi Babysit: address PR #9598 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
…launch (#9606)

Every warm workbench call ran a separate CLI integrity check command (ownership
write, launch, stop receipt, settle) before the user's command, about a third of
a warm call's fixed cost.

- The session supervisor's PID 1 now runs the same check as the ordinary user,
  after recording itself and before starting the command; a mismatch starts
  nothing and is reported on the stop receipt the call already fetches
- A refused launch is repaired under the session lock and relaunched once; a
  second refusal fails the call
- The check runs with an isolated interpreter instead of the user's login shell,
  never follows links or blocks on non-regular files, and treats an unreadable
  artifact as stale so repair overwrites it
- A reused machine runs no separate check command; a new machine still installs
  the CLI first
- Gated live test against real E2B covers refusal, repair, hooks, FIFOs, Stop
  during a slow check, and the warm-call cost
* fix(organizations): let admins rename their organization

* fix(organizations): keep the rename draft until it is saved or cancelled
* fix(credentials): let members leave a shared credential

* fix(credentials): type the stale keys list

* fix(credentials): match the last-admin guard to the server
… operation and let Chat rename workspaces (#9607)

* improvement(workspaces): move workspace updates behind an application operation and let Chat rename workspaces

PATCH/PUT /api/workspaces/[id] now run through defineInternalJsonRoute and the
session-only workspaces.update use case, keeping the legacy 403/400/500 bodies,
audit changes and response shape. Chat gets a workspace/general settings section
backed by workspaces.identity.read and workspaces.identity.update, which admit
only name and logo; billing payer and the personal-key policy stay session-only.

* fix(workspaces): recheck active state atomically on update

The workspace UPDATE now carries the not-archived predicate, so an archival
that commits after the use case's reads leaves nothing to write and answers
the same 404 as a missing workspace, on both the session route and Chat's
identity update. The integration suite holds the row lock, archives the row
while the update waits, and asserts the 404 and the unchanged name; the
archived-fixture refusal now checks that fixture's own row.
… out other devices (#9595)

* fix(organizations): let members leave their organization; remove sign out other devices

* fix(organizations): hide Leave when the caller's role is unknown

* fix(organizations): offer Leave only from an authoritative role

* fix(organizations): use only a freshly fetched summary role for Leave

* fix(organizations): refetch the summary role on mount
…ess connector (#9602)

* fix(knowledge): let members connect their own account to a member-access connector

* fix(knowledge): keep watching member enrollment until it connects
…tic card above the composer (#9608)

* improvement(billing): open a Chat plan popup on send instead of a static card above the composer

* improvement(billing): name the plan upgrade link input
…9597)

* fix(sim-links): hand off a departing member's links to the successor

* fix(sim-links): repair leftover links on account deletion

* fix(sim-links): lock leftover-link organizations before deletion barriers

* fix(sim-links): hold the creator's membership while creating a link
* improvement(chat): let Chat review organization access requests

Organization admins can list, preview, approve and deny access requests from an
organization conversation through the settings tool's new requests section.
The admin list, preview and resolve operations now admit organization-delegated
Copilot principals under the sim:settings audience; role, audit, outbox notice
and fingerprint checks are unchanged. Request settings stay session-only.

* fix(chat): default access request list to pending

The settings tool parses list input before calling the reader, so an omitted
status reached it as undefined and listed every status. The list schema now
defaults status to pending, matching get; explicit statuses still apply.
…nd keep the renamed name visible (#9609)

* fix(organizations): refuse self-leave for directory-managed members and keep the renamed name visible

* fix(organizations): refuse transfer-and-leave for directory-managed owners

* fix(organizations): transfer ownership for directory-managed owners and explain why they stay
…te them (#9611)

* fix(db): bootstrap Projects for development push databases that predate them

A push-managed database with workspaces but no Project schema could never pass
the schema-push guard: push cannot backfill workspace.project_id, and the
guard refuses every unenforced Project state. Under SIM_DEV_DB_PUSH=1 the
pre-push reconciler now replays migrations 0394 and 0406 (the foundation
commits atomically with the rollout marker) and runs the registered
0032_project_membership script migration on its single session, recording it
as the migration runner does. An interrupted bootstrap resumes; every other
unenforced state still refuses with the same message.

The pinned drizzle-kit patch also adds unique constraints before foreign keys,
so a composite foreign key on a new unique constraint of an existing table no
longer fails, and in the noninteractive push policy it answers the
unique-constraint truncate prompt with its non-destructive choice.

* fix(db): push the schema before the Project migration reads it

The bootstrap ran 0032_project_membership before Drizzle pushed anything, so a
development database from before a column the migration reads (such as the
fork parent) failed after committing the expansion, on every retry.

--prepare now only replays the expansion. push.ts then runs one Drizzle push
with workspace.project_id still nullable (SIM_DB_PUSH_PROJECT_BOOTSTRAP=1),
runs the migration through reconcile-project-membership.ts --bootstrap, and
finishes with the ordinary push. The connector and rollout marker are kept out
of Drizzle's diff until the migration retires them. A fresh bootstrap also
requires that no public table carries a project_id column.

* fix(db): pin the Project bootstrap to the public schema

The replayed migration SQL and the script runner name tables unqualified, so a
connection search path that lists another schema first created the Project
foundation outside public. The refusal snapshot now also covers folders and
Projects.
@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Nov 3, 2026 5:13am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The PR appears safe to merge; no actionable issues were found.

Summary

Syncs dev with staging and adds a development-only path for databases that predate Projects.

  • Development database pushes can bootstrap Projects before enforcing membership.
  • Timed-out workbench runs report progress, and reused workbenches check their CLI before running commands.
  • Organization administrators can review access requests through Chat.
  • Organization Sim links stay available as members leave and change.
  • Organization members can leave through a confirmed, role-aware flow.
  • Credential member screens support clearer member actions and departure paths.
  • Organization account connections and settings have new navigation and actions.
  • Workspace updates use a shared path across workspace settings and APIs.
  • Chat shows a plan modal when a plan is needed to continue.
  • Knowledge-base connectors can enroll members in shared access.
  • Workspace permissions control who can change an MCP server destination or rename a table.
  • The workflow-platform guides and cover image have updated content.
  • Unused-export tracking and a user-profile query file receive cleanup updates.

No actionable issues were found. This review checked source and tests; it did not run the test suites.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Development schema push] --> B[Acquire migration fence]
  B --> C[Prepare Project bootstrap]
  C --> D{Bootstrap pending?}
  D -->|Yes| E[Push nullable Project column]
  E --> F[Run Project membership backfill]
  F --> G[Push final schema]
  D -->|No| G
  G --> H[Run shared reconcilers]
Loading

Reviews (1) · Last reviewed commit: "Merge remote-tracking branch 'origin/sta..." · Reviewed by Greptile

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

15 issues found across 110 files

Confidence score: 3/5

  • In membership.ts, last-workspace self-removal skips the SCIM guard because the caller omits the actor ID, allowing directory-managed members to remove their organization membership. Pass the actor identity on self-removals.
  • In update.ts, a concurrent admin demotion can leave the billed account without an admin because the check happens outside the transaction. Revalidate the candidate inside the transaction.
  • In session-cli.ts, a directory at a CLI artifact path is treated as a verification failure, so ensureSessionCli skips repair. Check the file type before wrapping the descriptor with os.fdopen.
  • In credentials.ts, a committed leave can leave fresh credential caches showing the user still has access if the DELETE response is lost. Invalidate those caches from onSettled.
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="apps/sim/app/workspace/[workspaceId]/skills/[skillId]/components/skill-editors-card.tsx">

<violation number="1" location="apps/sim/app/workspace/[workspaceId]/skills/[skillId]/components/skill-editors-card.tsx:55">
P3: The disabled Remove action does not explain why workspace admins cannot be removed. Pass `skillEditorLockReason(editor.isWorkspaceAdmin)` as `disabledReason` so the shared row shows the inheritance explanation on hover.</violation>
</file>

<file name="apps/sim/stores/modals/chat-plan/store.ts">

<violation number="1" location="apps/sim/stores/modals/chat-plan/store.ts:15">
P2: This singleton can leave the paid-plan dialog open after navigating away from the workspace that triggered it, including on organization routes where no chat send occurred. Clear the transient modal state on route/workspace changes or scope it to the initiating workspace.</violation>
</file>

<file name="apps/sim/lib/sim-links/application/create-membership.integration.ts">

<violation number="1" location="apps/sim/lib/sim-links/application/create-membership.integration.ts:83">
P2: This test does not exercise the required `FOR SHARE` lifetime: it removes the member before `createSimLink` starts its insert transaction, so a membership recheck without the lock would still pass. Add synchronization inside the creation transaction, or observe that removal blocks until creation commits, to cover the concurrent-removal case.

(Based on your team's feedback about rechecking membership and holding the member row through commit.)</violation>
</file>

<file name="apps/sim/lib/billing/organizations/membership.ts">

<violation number="1" location="apps/sim/lib/billing/organizations/membership.ts:1393">
P2: The last-workspace self-removal path bypasses this SCIM guard because its caller omits `actorUserId`; pass the actor identity for self-removals so directory-managed members cannot delete their organization membership through workspace removal.</violation>
</file>

<file name="apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-header/organization-header.tsx">

<violation number="1" location="apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-header/organization-header.tsx:105">
P2: The close guard leaves `isMenuOpen` true, and `stopRenaming` never closes it. After Enter, Escape, or a successful blur, the organization menu remains open; close it when stopping the edit.</violation>

<violation number="2" location="apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-header/organization-header.tsx:158">
P2: The controlled dropdown drops its close request while the rename input is mounted, but never replays it after blur or save. Clicking outside during rename therefore leaves the organization menu open, including for unchanged or successfully saved names. Record the pending close request and apply it after blur/save while keeping the menu open for failed saves.</violation>
</file>

<file name="apps/sim/app/workspace/[workspaceId]/components/credential-detail/components/credential-members-section.tsx">

<violation number="1" location="apps/sim/app/workspace/[workspaceId]/components/credential-detail/components/credential-members-section.tsx:103">
P2: While the session query is pending, this treats an admin's own row as another member and renders `Remove` instead of `Leave`. Gate both action branches on a resolved viewer ID; otherwise clicking during that window revokes the admin's membership without the leave confirmation.</violation>
</file>

<file name="apps/sim/lib/settings/sections.ts">

<violation number="1" location="apps/sim/lib/settings/sections.ts:58">
P2: This section is advertised by `settings list` and `settings open` even when Sim links are disabled for the organization. Gate discovery/open on the Sim links rollout flag (and membership) before exposing this section, rather than relying only on the later adapter operations to reject access.</violation>

<violation number="2" location="apps/sim/lib/settings/sections.ts:110">
P2: `settings open` cannot direct the user to the workspace name and logo page because this entry omits `uiSection`, so it returns neither `setupUrl` nor `userSetup`. Set `uiSection: 'general'` so the existing adapter and canonical settings route are exposed together.</violation>
</file>

<file name="apps/sim/lib/execution/remote-sandbox/session-cli.ts">

<violation number="1" location="apps/sim/lib/execution/remote-sandbox/session-cli.ts:23">
P2: A directory at either CLI artifact path is reported as verification failure instead of stale, because `os.fdopen` raises before the non-regular-file check; `ensureSessionCli` then skips repair. Inspect the descriptor with `fstat` before calling `fdopen` so directories exit 10 like other non-regular artifacts.</violation>
</file>

<file name="apps/sim/hooks/queries/credentials.ts">

<violation number="1" location="apps/sim/hooks/queries/credentials.ts:327">
P2: A committed leave can appear to fail when the DELETE response is lost, but `onSuccess` then leaves fresh credential caches showing the user still has access. Invalidate these caches from `onSettled` instead.</violation>
</file>

<file name="apps/sim/lib/mothership/tools/server/settings-operations.ts">

<violation number="1" location="apps/sim/lib/mothership/tools/server/settings-operations.ts:380">
P2: `bulk_add_members` rebuilds the v2 schema from `.shape`, dropping its cross-field validation; `describe` therefore advertises combinations that the operation rejects. Reuse the complete schema and extend it with `groupId`.</violation>
</file>

<file name="apps/sim/lib/workspaces/update.ts">

<violation number="1" location="apps/sim/lib/workspaces/update.ts:58">
P2: `previous` is not read atomically with the update, so concurrent same-field edits produce incorrect audit “from” values. Read and update under one row lock/transaction, or use a version predicate and derive the audit source from the locked row.</violation>

<violation number="2" location="apps/sim/lib/workspaces/update.ts:83">
P2: The billed-account admin check is outside the transaction, so a concurrent demotion can commit before the payer transfer and leave a non-admin billed account. Revalidate the candidate under the transaction’s mutation locks and abort when the permission changed before committing the transfer.</violation>
</file>

<file name="apps/sim/app/workspace/[workspaceId]/settings/components/organization-accounts/organization-accounts.tsx">

<violation number="1" location="apps/sim/app/workspace/[workspaceId]/settings/components/organization-accounts/organization-accounts.tsx:108">
P3: A refetch failure with a cached empty result is shown as a successful empty state, leaving the user no retry control. Gate this empty state on `!accounts.isError` so the error branch remains reachable.</violation>
</file>

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

Turn on auto-fix | Re-trigger cubic

* the send started (the composer, a queued message, a handoff from another surface), and the
* workspace shell renders the one modal.
*/
export const useChatPlanModalStore = create<ChatPlanModalState>()(

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This singleton can leave the paid-plan dialog open after navigating away from the workspace that triggered it, including on organization routes where no chat send occurred. Clear the transient modal state on route/workspace changes or scope it to the initiating workspace.

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 apps/sim/stores/modals/chat-plan/store.ts, line 15:

<comment>This singleton can leave the paid-plan dialog open after navigating away from the workspace that triggered it, including on organization routes where no chat send occurred. Clear the transient modal state on route/workspace changes or scope it to the initiating workspace.</comment>

<file context>
@@ -0,0 +1,24 @@
+ * the send started (the composer, a queued message, a handoff from another surface), and the
+ * workspace shell renders the one modal.
+ */
+export const useChatPlanModalStore = create<ChatPlanModalState>()(
+  devtools(
+    (set) => ({
</file context>
Fix with cubic

await authorized.promise
expect(
(
await removeUserFromOrganization({

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This test does not exercise the required FOR SHARE lifetime: it removes the member before createSimLink starts its insert transaction, so a membership recheck without the lock would still pass. Add synchronization inside the creation transaction, or observe that removal blocks until creation commits, to cover the concurrent-removal case.

(Based on your team's feedback about rechecking membership and holding the member row through commit.)

View Feedback

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 apps/sim/lib/sim-links/application/create-membership.integration.ts, line 83:

<comment>This test does not exercise the required `FOR SHARE` lifetime: it removes the member before `createSimLink` starts its insert transaction, so a membership recheck without the lock would still pass. Add synchronization inside the creation transaction, or observe that removal blocks until creation commits, to cover the concurrent-removal case.

(Based on your team's feedback about rechecking membership and holding the member row through commit.) </comment>

<file context>
@@ -0,0 +1,101 @@
+  await authorized.promise
+  expect(
+    (
+      await removeUserFromOrganization({
+        organizationId,
+        userId: creatorId,
</file context>
Fix with cubic

if (actorUserId)
if (actorUserId) {
await requireMemberManagementAuthority(tx, organizationId, actorUserId, userId)
if (actorUserId === userId)

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The last-workspace self-removal path bypasses this SCIM guard because its caller omits actorUserId; pass the actor identity for self-removals so directory-managed members cannot delete their organization membership through workspace removal.

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 apps/sim/lib/billing/organizations/membership.ts, line 1393:

<comment>The last-workspace self-removal path bypasses this SCIM guard because its caller omits `actorUserId`; pass the actor identity for self-removals so directory-managed members cannot delete their organization membership through workspace removal.</comment>

<file context>
@@ -1384,8 +1388,11 @@ export async function removeUserFromOrganization(
-        if (actorUserId)
+        if (actorUserId) {
           await requireMemberManagementAuthority(tx, organizationId, actorUserId, userId)
+          if (actorUserId === userId)
+            await assertSelfLeaveNotScimManaged({ organizationId, userId, executor: tx })
+        }
</file context>
Fix with cubic

Comment on lines +105 to +108
const stopRenaming = () => {
setDraftName(null)
renameOrganization.reset()
}

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The close guard leaves isMenuOpen true, and stopRenaming never closes it. After Enter, Escape, or a successful blur, the organization menu remains open; close it when stopping the edit.

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 apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-header/organization-header.tsx, line 105:

<comment>The close guard leaves `isMenuOpen` true, and `stopRenaming` never closes it. After Enter, Escape, or a successful blur, the organization menu remains open; close it when stopping the edit.</comment>

<file context>
@@ -85,13 +102,37 @@ export function OrganizationHeader({
   }
 
   const { memberCount } = organization
+  const stopRenaming = () => {
+    setDraftName(null)
+    renameOrganization.reset()
</file context>
Suggested change
const stopRenaming = () => {
setDraftName(null)
renameOrganization.reset()
}
const stopRenaming = () => {
setDraftName(null)
setIsMenuOpen(false)
renameOrganization.reset()
}
Fix with cubic

Comment on lines +103 to +119
const action: MemberRowAction | null =
member.userId === viewerId
? {
label: 'Leave',
disabled: actionDisabled,
disabledReason:
lockReason ??
(roleLocked ? 'Make someone else an admin before leaving' : null),
onSelect: () => setIsLeaveConfirmOpen(true),
}
: isAdmin
? {
label: 'Remove',
disabled: actionDisabled,
onSelect: () => handleRemoveMember(member.userId),
}
: null

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: While the session query is pending, this treats an admin's own row as another member and renders Remove instead of Leave. Gate both action branches on a resolved viewer ID; otherwise clicking during that window revokes the admin's membership without the leave confirmation.

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 apps/sim/app/workspace/[workspaceId]/components/credential-detail/components/credential-members-section.tsx, line 103:

<comment>While the session query is pending, this treats an admin's own row as another member and renders `Remove` instead of `Leave`. Gate both action branches on a resolved viewer ID; otherwise clicking during that window revokes the admin's membership without the leave confirmation.</comment>

<file context>
@@ -64,22 +99,56 @@ export function CredentialMembersSection({ credentialId, isAdmin }: CredentialMe
               member.roleSource !== 'workspace-admin' &&
               explicitAdminCount <= 1
+            const actionDisabled = roleLocked || lockReason !== null
+            const action: MemberRowAction | null =
+              member.userId === viewerId
+                ? {
</file context>
Suggested change
const action: MemberRowAction | null =
member.userId === viewerId
? {
label: 'Leave',
disabled: actionDisabled,
disabledReason:
lockReason ??
(roleLocked ? 'Make someone else an admin before leaving' : null),
onSelect: () => setIsLeaveConfirmOpen(true),
}
: isAdmin
? {
label: 'Remove',
disabled: actionDisabled,
onSelect: () => handleRemoveMember(member.userId),
}
: null
const action: MemberRowAction | null =
viewerId !== undefined && member.userId === viewerId
? {
label: 'Leave',
disabled: actionDisabled,
disabledReason:
lockReason ??
(roleLocked ? 'Make someone else an admin before leaving' : null),
onSelect: () => setIsLeaveConfirmOpen(true),
}
: viewerId !== undefined && isAdmin
? {
label: 'Remove',
disabled: actionDisabled,
onSelect: () => handleRemoveMember(member.userId),
}
: null
Fix with cubic

throw new OrchestrationError('validation', 'No updates provided')
}

const [previous] = await db

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: previous is not read atomically with the update, so concurrent same-field edits produce incorrect audit “from” values. Read and update under one row lock/transaction, or use a version predicate and derive the audit source from the locked 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 apps/sim/lib/workspaces/update.ts, line 58:

<comment>`previous` is not read atomically with the update, so concurrent same-field edits produce incorrect audit “from” values. Read and update under one row lock/transaction, or use a version predicate and derive the audit source from the locked row.</comment>

<file context>
@@ -0,0 +1,128 @@
+    throw new OrchestrationError('validation', 'No updates provided')
+  }
+
+  const [previous] = await db
+    .select()
+    .from(workspace)
</file context>
Fix with cubic

'Personal workspaces are always billed to their owner and cannot change billed account.'
)
}
const candidatePermission = await getEffectiveWorkspacePermission(billedAccountUserId, previous)

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The billed-account admin check is outside the transaction, so a concurrent demotion can commit before the payer transfer and leave a non-admin billed account. Revalidate the candidate under the transaction’s mutation locks and abort when the permission changed before committing the transfer.

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 apps/sim/lib/workspaces/update.ts, line 83:

<comment>The billed-account admin check is outside the transaction, so a concurrent demotion can commit before the payer transfer and leave a non-admin billed account. Revalidate the candidate under the transaction’s mutation locks and abort when the permission changed before committing the transfer.</comment>

<file context>
@@ -0,0 +1,128 @@
+        'Personal workspaces are always billed to their owner and cannot change billed account.'
+      )
+    }
+    const candidatePermission = await getEffectiveWorkspacePermission(billedAccountUserId, previous)
+    if (candidatePermission !== 'admin') {
+      throw new OrchestrationError('validation', 'Billed account must be a workspace admin')
</file context>
Fix with cubic

open={isMenuOpen}
onOpenChange={(open) => {
// The input's blur saves the draft; closing now would unmount it and hide a failure.
if (!open && draftName !== null) return

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The controlled dropdown drops its close request while the rename input is mounted, but never replays it after blur or save. Clicking outside during rename therefore leaves the organization menu open, including for unchanged or successfully saved names. Record the pending close request and apply it after blur/save while keeping the menu open for failed saves.

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 apps/sim/app/o/[organizationId]/components/organization-sidebar/components/organization-header/organization-header.tsx, line 158:

<comment>The controlled dropdown drops its close request while the rename input is mounted, but never replays it after blur or save. Clicking outside during rename therefore leaves the organization menu open, including for unchanged or successfully saved names. Record the pending close request and apply it after blur/save while keeping the menu open for failed saves.</comment>

<file context>
@@ -110,25 +151,34 @@ export function OrganizationHeader({
+        open={isMenuOpen}
+        onOpenChange={(open) => {
+          // The input's blur saves the draft; closing now would unmount it and hide a failure.
+          if (!open && draftName !== null) return
+          setIsMenuOpen(open)
+        }}
</file context>
Fix with cubic

canEdit
? {
label: 'Remove',
disabled: editor.isWorkspaceAdmin,

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The disabled Remove action does not explain why workspace admins cannot be removed. Pass skillEditorLockReason(editor.isWorkspaceAdmin) as disabledReason so the shared row shows the inheritance explanation on hover.

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 apps/sim/app/workspace/[workspaceId]/skills/[skillId]/components/skill-editors-card.tsx, line 55:

<comment>The disabled Remove action does not explain why workspace admins cannot be removed. Pass `skillEditorLockReason(editor.isWorkspaceAdmin)` as `disabledReason` so the shared row shows the inheritance explanation on hover.</comment>

<file context>
@@ -47,10 +47,17 @@ export function SkillEditorsCard({ editors, canEdit }: SkillEditorsCardProps) {
+                canEdit
+                  ? {
+                      label: 'Remove',
+                      disabled: editor.isWorkspaceAdmin,
+                      onSelect: () => editors.removeEditor(editor.userId),
+                    }
</file context>
Suggested change
disabled: editor.isWorkspaceAdmin,
disabled: editor.isWorkspaceAdmin,
disabledReason: skillEditorLockReason(editor.isWorkspaceAdmin),
Fix with cubic

/>
) : accounts.isPending ? (
<SettingsEmptyState>Loading connected accounts…</SettingsEmptyState>
) : list.length === 0 ? (

@cubic-dev-ai cubic-dev-ai Bot Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: A refetch failure with a cached empty result is shown as a successful empty state, leaving the user no retry control. Gate this empty state on !accounts.isError so the error branch remains reachable.

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 apps/sim/app/workspace/[workspaceId]/settings/components/organization-accounts/organization-accounts.tsx, line 108:

<comment>A refetch failure with a cached empty result is shown as a successful empty state, leaving the user no retry control. Gate this empty state on `!accounts.isError` so the error branch remains reachable.</comment>

<file context>
@@ -0,0 +1,199 @@
+          />
+        ) : accounts.isPending ? (
+          <SettingsEmptyState>Loading connected accounts…</SettingsEmptyState>
+        ) : list.length === 0 ? (
+          <SettingsEmptyState>No accounts connected for an organization</SettingsEmptyState>
+        ) : (
</file context>
Suggested change
) : list.length === 0 ? (
) : list.length === 0 && !accounts.isError ? (
Fix with cubic

@waleedlatif1
waleedlatif1 merged commit a775c7b into dev Nov 3, 2026
81 checks passed
@waleedlatif1
waleedlatif1 deleted the chore/sync-dev-with-staging-2026-11-03b branch November 3, 2026 05:52

This branch was successfully deployed

1 active deployment
Preview — 5a315681 Deployed Nov 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant