Skip to content

Fix stale workspace resolution#1349

Open
khaliqgant wants to merge 1 commit into
mainfrom
fix/workspace-active-self-heal
Open

Fix stale workspace resolution#1349
khaliqgant wants to merge 1 commit into
mainfrom
fix/workspace-active-self-heal

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jul 22, 2026

Copy link
Copy Markdown
Member

What changed

  • recover stale active workspace keys from the authenticated Cloud workspace
  • bootstrap an empty workspace store for agent-relay workspace active
  • fall back to the membership-aware workspace join contract when dedicated token routes are unavailable
  • preserve strict fail-closed behavior for SDK callers that do not opt into empty-store bootstrapping
  • persist and activate the recovered canonical workspace key before retrying resolution

Root cause

Cloud authentication and local workspace selection are separate state. A valid agent-relay cloud login could coexist with a global active alias that referenced a deleted workspace. workspace active trusted only that stale alias, so every resolve endpoint returned 404 even though cloud whoami reported a valid current workspace. Current Cloud deployments expose canonical workspace recovery through the authenticated /join contract, while the dedicated token issue compatibility routes can be absent.

User impact

agent-relay workspace active now repairs stale or missing local workspace state from the user's authenticated Cloud workspace. SDK consumers also recover stale keys, while callers that require an explicitly configured key still fail closed when the store is empty.

Validation

  • npm run typecheck
  • full isolated unit suite: 113 test files passed, 1,428 tests passed, 25 skipped
  • focused workspace and strict integration tests: 61 passed
  • live Cloud E2E with an intentionally stale isolated workspace store
  • live Cloud E2E with a completely empty isolated workspace store
  • verified repaired stores use the canonical Default workspace and owner-only 0600 permissions

Review in cubic

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Workspace bootstrap and repair

Layer / File(s) Summary
Join and token recovery contract
packages/cloud/src/workspaces.ts, packages/cloud/src/workspaces.test.ts
Workspace key recovery uses the canonical /join endpoint, including token fallback and normalized agent-name request handling.
Active workspace resolution repair
packages/cloud/src/workspaces.ts, packages/cloud/src/workspaces.test.ts
Active workspace resolution can authenticate, join the current cloud workspace, persist recovered state, and resolve it again after missing or stale local data.
CLI bootstrap wiring and validation
packages/cli/src/cli/commands/workspace.ts, packages/cli/src/cli/commands/workspace.test.ts, packages/cloud/src/workspaces.test.ts
The CLI enables bootstrapFromCloud, with tests covering the updated command arguments and cloud workspace flows.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: willwashburn

Poem

A rabbit found a workspace key,
Lost in clouds but not for long.
It joined the den and wrote it down,
Then resolved the path where it belonged.
bootstrap: true — hop, hop, hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: fixing stale workspace resolution.
Description check ✅ Passed The description includes the change, root cause, impact, and validation, though it uses custom headings instead of the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/workspace-active-self-heal

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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

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

@kjgbot
kjgbot marked this pull request as ready for review July 22, 2026 13:53
@kjgbot
kjgbot requested a review from willwashburn as a code owner July 22, 2026 13:53
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

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

Actionable comments posted: 1

🤖 Prompt for all review comments with 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.

Inline comments:
In `@packages/cloud/src/workspaces.ts`:
- Around line 420-451: Update the join repair flow in repairActiveWorkspace and
joinWorkspaceKey so interactive and refreshTimeoutMs from
ResolveActiveWorkspaceOptions are included in the helper options and forwarded
through tryPostJson. Preserve the caller-provided values, including interactive:
false, to prevent browser-login fallback and honor the configured timeout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 66aeb9a5-9244-47f9-b183-6b2bf0fe3e4b

📥 Commits

Reviewing files that changed from the base of the PR and between bdef84f and d357349.

📒 Files selected for processing (4)
  • packages/cli/src/cli/commands/workspace.test.ts
  • packages/cli/src/cli/commands/workspace.ts
  • packages/cloud/src/workspaces.test.ts
  • packages/cloud/src/workspaces.ts

Comment on lines +420 to +451
async function repairActiveWorkspace(
options: ResolveActiveWorkspaceOptions
): Promise<ActiveWorkspaceDescriptor> {
const apiUrl = options.apiUrl || defaultApiUrl();
const auth = await ensureAuthenticated(apiUrl, {
interactive: options.interactive ?? false,
refreshTimeoutMs: options.refreshTimeoutMs,
});
const { response } = await authorizedApiFetch(
auth,
'/api/v1/auth/whoami',
{ method: 'GET' },
{
interactive: options.interactive ?? false,
refreshTimeoutMs: options.refreshTimeoutMs,
}
);
const payload = (await readJson(response)) as (WhoAmIResponse & { error?: string }) | null;
if (!response.ok || !payload?.authenticated || !payload.currentWorkspace?.id) {
const detail = payload?.error ?? (response.statusText || 'Failed to resolve auth status');
throw new Error(`Active workspace repair failed: ${response.status} ${detail}`.trim());
}

const workspaceName = payload.currentWorkspace.name?.trim() || payload.currentWorkspace.id;
const workspaceKey = await joinWorkspaceKey(payload.currentWorkspace.id, 'agent-relay-cli', {
apiUrl: auth.apiUrl,
});
setWorkspaceKey(workspaceName, workspaceKey, options.env);
setActiveWorkspace(workspaceName, options.env);

return resolveWorkspaceKey(workspaceKey, options);
}

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect WorkspaceClientOptions / tryPostJson / tryGetJson to confirm default
# `interactive` behavior when the field is omitted.
rg -n -B2 -A 20 'type WorkspaceClientOptions|async function tryPostJson|async function tryGetJson' packages/cloud/src/workspaces.ts

Repository: AgentWorkforce/relay

Length of output: 2175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect joinWorkspaceKey and its callers for option threading and defaults.
ast-grep outline packages/cloud/src/workspaces.ts --view expanded
printf '\n--- joinWorkspaceKey references ---\n'
rg -n -A 20 -B 10 'joinWorkspaceKey\(' packages/cloud/src/workspaces.ts
printf '\n--- active workspace callers ---\n'
rg -n -A 6 -B 6 'repairActiveWorkspace|resolveWorkspaceKey\(' packages/cloud/src/workspaces.ts

Repository: AgentWorkforce/relay

Length of output: 7544


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect ensureAuthenticated defaults and whether joinWorkspaceKey uses the same auth path.
rg -n -A 25 -B 10 'async function ensureAuthenticated|function ensureAuthenticated|joinWorkspaceKey\(' packages/cloud/src

Repository: AgentWorkforce/relay

Length of output: 10465


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the joinWorkspaceKey definition and the nearby auth helpers.
ast-grep outline packages/cloud/src/workspaces.ts --view expanded | sed -n '1,220p'
printf '\n--- definition excerpt ---\n'
sed -n '300,470p' packages/cloud/src/workspaces.ts | cat -n

Repository: AgentWorkforce/relay

Length of output: 8556


Thread interactive/refreshTimeoutMs through the join repair path. repairActiveWorkspace() passes interactive: false, but joinWorkspaceKey()tryPostJson() drops both auth flags, so this can still fall back to browser login and ignore the caller timeout. Update the helper options and forward them here.

🤖 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 `@packages/cloud/src/workspaces.ts` around lines 420 - 451, Update the join
repair flow in repairActiveWorkspace and joinWorkspaceKey so interactive and
refreshTimeoutMs from ResolveActiveWorkspaceOptions are included in the helper
options and forwarded through tryPostJson. Preserve the caller-provided values,
including interactive: false, to prevent browser-login fallback and honor the
configured timeout.

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