Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/workspaces.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ test("a checkout exposes initial and nested instruction context while filtering
const context = await fixture(t);
const opened = await context.registry.openWorkspace(context.root);

assert.match(opened.workspace.id, /^ws_[a-f0-9]{10}$/);
assert.equal(opened.workspace.mode, "checkout");
assert.deepEqual(
opened.agentsFiles.map((file) => file.content),
Expand Down
4 changes: 2 additions & 2 deletions src/workspaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { randomUUID } from "node:crypto";
import { randomBytes } from "node:crypto";
import type { Stats } from "node:fs";
import type {
WorkspaceConversationBinding,
Expand Down Expand Up @@ -355,7 +355,7 @@ export class WorkspaceRegistry {
worktree?: WorkspaceWorktree;
}): Promise<WorkspaceContext> {
const workspace: Workspace = {
id: `ws_${randomUUID()}`,
id: `ws_${randomBytes(5).toString("hex")}`,

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 Handle compact ID collisions

The new 40-bit ID space has no collision retry: a duplicate causes the persisted workspace insert to fail with a primary-key error, while registries without a store silently replace the existing workspace in the in-memory map.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Narrow static proxy. It does not run packaged npm/npx installs or MCP hosts.
rg -n --glob '!node_modules/**' \
  -P '\b(createSession|getSession|getWorkspace|workspaceId|WorkspaceStore|randomUUID|randomBytes)\b' .

Repository: Waishnav/devspace

Length of output: 28095


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/workspace-store.ts outline =="
ast-grep outline src/workspace-store.ts --view expanded || true

echo "== src/workspace-store.ts relevant sections =="
sed -n '1,140p' src/workspace-store.ts
sed -n '180,225p' src/workspace-store.ts

echo "== src/workspaces.ts outline =="
ast-grep outline src/workspaces.ts --view expanded || true

echo "== src/workspaces.ts relevant sections =="
sed -n '1,120p' src/workspaces.ts
sed -n '230,390p' src/workspaces.ts

echo "== deterministic capacity/collision proxy =="
python3 - <<'PY'
from math import sqrt
import math
space = 2**40
# approximate birthday median / sqrt(2*N*ln(2)) when p=0.5
median = int(sqrt(2 * space * math.log(2))) + 1
print(f"id_space={space}")
print(f"estimated_approximate_sessions_for_50_percent_collision={median}")
print(f"hex_bytes_generated={5}")
print(f"expected_replacements_per_unique_workspace={space/(space-median)**2:.1e}" if False else "")
# Show tiny probabilities for small n via Poisson approximation n^2/(2 space).
for n in [1, 10, 1_000, 10_000]:
    n = int(n)
    p = n*(n-1)/(2*space)
    print(f"n={n}, expected_pairs={p:.2e}, p_collision_lower_bound_by_pairs={min(p,1):.6e}")
PY

Repository: Waishnav/devspace

Length of output: 19695


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/workspace-store.ts createSession full relevant block with line numbers =="
nl -ba src/workspace-store.ts | sed -n '66,88p'

echo "== database schema workspace session constraints =="
nl -ba src/db/schema.ts | sed -n '70,135p'

echo "== workspace ID consumers around collision handling =="
nl -ba src/workspaces.ts | sed -n '90,185p'
nl -ba src/server.test.ts | sed -n '60,180p' | sed -n '25,170p'

Repository: Waishnav/devspace

Length of output: 276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/workspace-store.ts createSession full relevant block with line numbers =="
awk '{printf "%6d\t%s\n", NR, $0}' src/workspace-store.ts | sed -n '66,88p'

echo "== database schema workspace session constraints =="
awk '{printf "%6d\t%s\n", NR, $0}' src/db/schema.ts | sed -n '70,135p'

echo "== workspace ID consumers around collision handling =="
awk '{printf "%6d\t%s\n", NR, $0}' src/workspaces.ts | sed -n '90,185p'
awk '{printf "%6d\t%s\n", NR, $0}' src/server.test.ts | sed -n '60,180p'

Repository: Waishnav/devspace

Length of output: 13915


Make the 40-bit workspace ID space collision-safe.

randomBytes(5).toString("hex") creates only 2^40 possible IDs. A collision can replace an existing workspace in this.workspaces.set(workspace.id, workspace) and can also overwrite the existing workspace session with the new workspace root/data. Use a collision-safe ID namespace, or enforce uniqueness atomically with retry before returning the workspace. The current tests do not cover ID collisions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/workspaces.ts` at line 358, Update workspace creation around the ID
generation in the workspace factory to prevent collisions from overwriting
existing entries or sessions. Use a larger collision-safe ID namespace, or
atomically check the generated ID against this.workspaces and retry before
inserting and returning the workspace; preserve the existing workspace and
session data when a collision occurs.

root: input.root,
mode: input.mode,
sourceRoot: input.sourceRoot,
Expand Down
Loading