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

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

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly matches the main change: fixing stale workspace resolution.
Description check✅ PassedThe description includes the change, root cause, impact, and validation, though it uses custom headings instead of the template.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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
@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.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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 freeto 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

@khaliqgant
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
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

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

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly matches the main change: fixing stale workspace resolution.
Description check✅ PassedThe description includes the change, root cause, impact, and validation, though it uses custom headings instead of the template.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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
@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.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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 freeto 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

@khaliqgant
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

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

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly matches the main change: fixing stale workspace resolution.
Description check✅ PassedThe description includes the change, root cause, impact, and validation, though it uses custom headings instead of the template.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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
@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.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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 freeto 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

@khaliqgant
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

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

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly matches the main change: fixing stale workspace resolution.
Description check✅ PassedThe description includes the change, root cause, impact, and validation, though it uses custom headings instead of the template.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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
@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.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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 freeto 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

@khaliqgant
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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

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

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly matches the main change: fixing stale workspace resolution.
Description check✅ PassedThe description includes the change, root cause, impact, and validation, though it uses custom headings instead of the template.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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
@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.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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 freeto 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

@khaliqgant
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

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

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly matches the main change: fixing stale workspace resolution.
Description check✅ PassedThe description includes the change, root cause, impact, and validation, though it uses custom headings instead of the template.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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
@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.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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 freeto 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

@khaliqgant
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

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

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly matches the main change: fixing stale workspace resolution.
Description check✅ PassedThe description includes the change, root cause, impact, and validation, though it uses custom headings instead of the template.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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
@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.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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 freeto 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

@khaliqgant
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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

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

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title clearly matches the main change: fixing stale workspace resolution.
Description check✅ PassedThe description includes the change, root cause, impact, and validation, though it uses custom headings instead of the template.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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
@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.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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 freeto 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

@khaliqgant