Skip to content

feat: add permission-aware cross-workspace access - #275

Merged
pedronauck merged 12 commits into
mainfrom
cross-workspaces
Jul 29, 2026
Merged

pedronauck merged 12 commits into
mainfrom
cross-workspaces

Conversation

@pedronauck

@pedronauck pedronauck commented Jul 29, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR delivers the complete seven-task cross-workspace access program. It replaces scattered
hard-deny behavior with one permission-mode-driven policy, wires that policy through every shipped
agent access seam, adds a safe foreign-session deep-link flow, and co-ships the public contracts,
documentation, official Compozy skill guidance, QA plans, and durable verification evidence.

The operator-facing policy is intentionally simple:

Session permission mode Cross-workspace behavior
approve-all Allow the agent to cross workspace boundaries without a prompt.
deny-all Deny every cross-workspace request without prompting.
approve-reads Prompt at the ACP native-tool seam; deny with an actionable hint at non-interactive seams. Session-scoped answers apply across seams until the session stops.

No new config key, database table, migration, durable grant, Settings section, or standalone access
management API is introduced. The existing session permissions.mode remains the single source of
truth.

Release notes

  • Agents can use the existing permission modes to access explicitly targeted workspaces, including
    public spawn targets through the API/UDS contract and compozy spawn --workspace.
  • Opening a session permalink owned by another workspace now shows a confirmation before switching;
    foreign session details never enter workspace-scoped caches before confirmation.
  • Cross-workspace decisions now produce attributable workspace.access_* audit events, and denials
    use consistent, actionable reasons across native tools, CLI, HTTP, and UDS.

What changed

1. Central workspace-access policy

  • Added internal/workspaceaccess as the single policy boundary with canonical target validation,
    actor-kind gating, fail-closed mode lookup, session-consent evaluation, and one best-effort audit
    record per decision.
  • Added daemon-owned implementations for permission-mode lookup, the volatile session consent cache,
    audit emission, ACP permission prompts, session-stop cleanup, and fail-closed dependency wiring.
  • Preserved identity checks as hard boundaries: session, agent, and actor-kind mismatches cannot be
    widened by workspace permission mode.
  • Accepted both canonical workspace ID forms used by the registry (ws_<16 lowercase hex> and
    Crockford ULIDs), while resolving aliases and paths before authorization.

2. Enforcement across runtime and agent-managed surfaces

  • Routed agent identity, task resource access and claims, tool/native dispatch, spawn, coordination,
    automation, and Network agent commands through the shared policy on workspace mismatch.
  • Kept operator semantics and trusted unbound-daemon behavior intact while making global/all native
    scopes unavailable to identified sessions, including callers that supply an explicit home
    workspace.
  • Authorized Network agent commands before resolving foreign resource details, preventing existence
    leakage on denied requests.
  • Preserved spawn's two-stage validation and judged foreign child targets using the parent session's
    effective mode; added the explicit workspace field to the public spawn contract and CLI.
  • Propagated workspace ownership through trusted automation actors and preserved validated agent
    credentials across the coordination CLI's UDS transport.

3. Prompt, consent, denial, and audit behavior

  • Added the ACP-native prompt options allow_once, allow_session, reject_once, and
    reject_session for approve-reads sessions.
  • Stored session decisions only in daemon memory; they are shared by all seams for that live session
    and are cleared on session stop or daemon restart. Timeouts, transport failures, and invalid
    answers deny without persisting consent.
  • Added the dedicated workspace-access denial reason and mode-specific remediation hint while
    preserving existing status codes, sentinels, and 404 masking where those surfaces already use it.
  • Normalized audit attribution to the actor's home workspace and exposed the same decisions through
    the existing event/log readers without turning audit data into authorization state.

4. Session-owner projection and Web deep links

  • Added GET /api/sessions/:session_id/owner with byte-identical HTTP/UDS behavior and the minimal
    three-field response {session_id, workspace_id, workspace_name}; unknown sessions remain 404.
  • Updated OpenAPI and generated TypeScript contracts for the owner projection and spawn target.
  • Routed canonical and short session permalinks through the owner projection. Foreign links show a
    workspace-switch confirmation; confirm uses the existing switch barrier and opens the exact
    session, while cancel preserves the current workspace and arrangement.
  • Kept owner and foreign session data out of workspace-scoped catalog/detail caches before consent.
  • Split fleet and agent-detail layouts into structural parents plus index leaves so search ownership
    remains with the session route, and hardened RoutingCoordinator so an older asynchronous focus
    completion cannot overwrite a newer accepted route.
  • Added the reusable confirmation dialog, route hold, Storybook coverage, router integration tests,
    and browser E2E coverage using existing @compozy/ui primitives.

5. Review remediation and live-QA fixes

  • Completed two deep-review rounds and resolved every actionable defect, advisory, and nit within the
    requested cap. The second round shipped with SHIP status.
  • Consolidated native caller authority in one classifier after the two-touch threshold, preventing
    binder/authorizer drift while retaining operator and daemon behavior.
  • Split policy contracts and structural validation from policy execution to keep responsibilities
    bounded and production files below the repository cap.
  • Fixed the coordination CLI identity regression found in live QA so agent calls cannot silently
    become operator calls (4ef8e8c).
  • Fixed nearest-workspace discovery across case-insensitive and symlinked path aliases while keeping
    lexical containment as the fast path (4e81f17).
  • Closed the remaining boot, extension, hosted-task, automation, test-fixture, and denial-contract
    gaps exposed by runtime E2E and the final monorepo verification.

6. Documentation and durable QA assets

  • Updated the session permission, workspace isolation/resolution, safe spawn, config, hook-event,
    and CLI reference documentation to match runtime behavior.
  • Updated the canonical glossary and the bundled skills/compozy/ references for permission modes,
    native-tool scope behavior, prompts, consent lifetime, and audit visibility.
  • Added/updated QA journeys, six selected scenarios, four persona-driven charters, two content-
    addressed bug records, and the dated run report.
  • Reset affected user-visible scenarios to untested where required for the next independent QA
    cycle rather than treating implementation verification as tracker retesting.

Architecture and data decisions

  • Cross-workspace access is an agent-session policy. Human operators retain existing access;
    extension, automation, Network-peer, and daemon actors do not gain mode-based widening.
  • Session consent is ephemeral and session-scoped. There is no new persisted authorization datum and
    no compatibility or migration path.
  • Audit is append-only observability, not authorization state; audit append failure is logged and
    does not change the decision.
  • Workspace references are resolved to canonical registry IDs before policy evaluation.
  • The owner endpoint deliberately returns only the data needed to decide whether to switch.

Compozy Impact Audit

  • Native tools: changed workspace binding/authority classification, policy enforcement, denial
    reasons, prompt handling, and focused tests. No tool IDs, toolsets, descriptors, I/O schemas,
    schema digests, risk flags, availability diagnostics, or capability gates changed.
  • Extensibility and hooks: no new extension surface, hook, capability, bundle, registry, bridge
    SDK, MCP sidecar, or config lifecycle was added. Existing extension/hosted-MCP fixtures were
    updated only to preserve fail-closed runtime wiring. The official bundled skill documents the
    changed public behavior.
  • Workspace data isolation: the authorization datum is agent/session-scoped; consent is volatile
    and keyed by session ID. Actor-home workspace IDs now propagate through CLI/HTTP/UDS/core/task/
    automation/native paths. Foreign session details are not read or cached before Web confirmation,
    and the global owner projection exposes only three fields. No new persisted data can leak across
    workspaces.
  • Official Compozy skill: updated skills/compozy/references/agent-definitions.md and
    skills/compozy/references/native-tools.md for mode behavior, target selection, native scope,
    prompts, consent, denials, and auditing.

Verification

  • make verify — PASS, exit 0 after both review rounds.
  • make lint — PASS, zero warnings/errors.
  • make codegen-check — PASS.
  • make test-e2e-runtime — PASS.
  • make test-e2e-web — PASS, unchanged 113-case catalog.
  • Focused race suites — PASS, including 1,329 CLI cases, 107 workspace cases, policy/seam
    integration, API HTTP/UDS parity, native authority, task/automation, and review regressions.
  • Web Turbo lane — PASS, 516 files and 4,060 tests plus build.
  • Site Turbo/build lane — PASS, 51 files, 248 tests, 1,845 generated pages, and clean anchors.
  • Isolated browser QA — PASS for both permalink forms, confirm/cancel/not-found behavior,
    reload/back/forward/multi-tab behavior, and pre-confirmation network isolation.
  • QA teardown — PASS with clean: true and no surviving daemon, Web, observer, browser, tmux, or
    watcher processes.

Detailed durable report: Cross-workspace access QA run.

Known limitations and QA disclosure

  • ADR-006 intentionally retains the current beta operator boundary: headerless loopback HTTP/UDS
    calls are trusted as operator requests. A same-UID malicious managed session can exploit that local
    trust boundary, and middleware without agent identity cannot guarantee a workspace-access audit.
    Closing this requires the future containment/operator-authority architecture, not another local
    seam patch.
  • Direct malicious edits to permission config files remain detection-oriented rather than OS-
    enforced containment. No agent-writable grant or access toggle was added by this PR.
  • All six selected cross-workspace scenarios passed or were fixed. The companion autonomous
    Northstar Pay run completed 12/12 tasks and 19 Network sends but its strict report remains
    FAIL on pre-existing, out-of-scope C11/C17 playbook gaps: 0/3 disruption probes delivered,
    1/2 review cycles completed, no evidenced resolved disagreement, and missing runtime-owned progress
    projection. The report preserves these facts instead of conflating them with feature coverage.

Scope

  • 7/7 implementation, documentation, planning, and QA tasks completed.
  • 11 commits across 218 files (8,645 insertions, 682 deletions).
  • No schema migration, new config key, durable grant, compatibility bridge, or hidden follow-up is
    part of this change.

Summary by CodeRabbit

  • New Features

    • Added policy-based access controls for cross-workspace sessions, tasks, tools, spawning, and network coordination.
    • Added workspace-aware session spawning through the --workspace option.
    • Added a session owner endpoint returning session and workspace details.
    • Added confirmation flows for opening sessions from another workspace, including permalink support.
    • Added workspace access consent handling and audit events.
  • Bug Fixes

    • Improved workspace identity resolution and canonical error messages.
    • Sanitized denied tool responses to prevent internal details from being exposed.

@greptile-apps

greptile-apps Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Too many files changed for review. (226 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

@vercel

vercel Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
compozy-site Ready Ready Preview Jul 29, 2026 9:10pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 26823b21-c31e-46fd-a29a-7d9e73cf2212

📥 Commits

Reviewing files that changed from the base of the PR and between a2ccd05 and 8de642d.

⛔ Files ignored due to path filters (25)
  • docs/_memory/glossary.md is excluded by !**/*.md
  • docs/qa/bugs/BUG-0028.md is excluded by !**/*.md
  • docs/qa/bugs/BUG-20260719-autonomous-progress-unobservable.md is excluded by !**/*.md
  • docs/qa/bugs/BUG-20260729-coordination-cli-drops-agent-identity.md is excluded by !**/*.md
  • docs/qa/bugs/BUG-20260729-nearest-workspace-case-alias.md is excluded by !**/*.md
  • docs/qa/charters/CH-cross-workspace-consent-audit.md is excluded by !**/*.md
  • docs/qa/charters/CH-cross-workspace-mode-seams.md is excluded by !**/*.md
  • docs/qa/charters/CH-foreign-session-deep-link.md is excluded by !**/*.md
  • docs/qa/charters/CH-workspace-binding-canary.md is excluded by !**/*.md
  • docs/qa/journeys/J-11-return-to-running-session.md is excluded by !**/*.md
  • docs/qa/journeys/J-12-open-session-fast.md is excluded by !**/*.md
  • docs/qa/journeys/J-cross-workspace-access.md is excluded by !**/*.md
  • docs/qa/journeys/J-open-foreign-session.md is excluded by !**/*.md
  • docs/qa/journeys/J-operate-workspace-context.md is excluded by !**/*.md
  • docs/qa/reports/2026-07-29-cross-workspace-access.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-native-workspace-scope-isolation.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-web-session-cross-workspace-confirm.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-web-session-deep-link-isolation.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-workspace-access-mode-matrix.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-workspace-access-prompt-outcomes.md is excluded by !**/*.md
  • docs/qa/scenarios/MS-workspace-resolution-chain.md is excluded by !**/*.md
  • docs/qa/scenarios/RT-073.md is excluded by !**/*.md
  • docs/qa/scenarios/TA-053.md is excluded by !**/*.md
  • internal/testutil/acpmock/testdata/workspace_access_fixture.json is excluded by !**/*.json
  • openapi/compozy.json is excluded by !**/*.json
📒 Files selected for processing (159)
  • internal/agentidentity/identity.go
  • internal/agentidentity/identity_test.go
  • internal/api/contract/agents.go
  • internal/api/contract/session_owner.go
  • internal/api/core/agent_channels_internal_test.go
  • internal/api/core/agent_create.go
  • internal/api/core/agent_definition_resolution.go
  • internal/api/core/agent_fleet_catalog.go
  • internal/api/core/agent_handlers.go
  • internal/api/core/agent_identity.go
  • internal/api/core/agent_spawn.go
  • internal/api/core/agent_spawn_test.go
  • internal/api/core/agent_task_claim_criteria.go
  • internal/api/core/base_handlers.go
  • internal/api/core/handlers_agents.go
  • internal/api/core/handlers_test.go
  • internal/api/core/network_coordination_public_test.go
  • internal/api/core/network_workspace_access.go
  • internal/api/core/session_owner.go
  • internal/api/core/tasks_internal_test.go
  • internal/api/core/test_helpers_test.go
  • internal/api/core/tools_test.go
  • internal/api/httpapi/agent_context_test.go
  • internal/api/httpapi/handlers.go
  • internal/api/httpapi/handlers_test.go
  • internal/api/httpapi/routes.go
  • internal/api/httpapi/server.go
  • internal/api/httpapi/server_handler_config.go
  • internal/api/httpapi/session_routes.go
  • internal/api/spec/registry_sessions.go
  • internal/api/testutil/workspace_access_stub.go
  • internal/api/udsapi/agent_identity_test.go
  • internal/api/udsapi/handler_config.go
  • internal/api/udsapi/handlers_test.go
  • internal/api/udsapi/routes.go
  • internal/api/udsapi/server.go
  • internal/api/udsapi/server_handler_config.go
  • internal/api/udsapi/server_handlers.go
  • internal/api/udsapi/session_routes.go
  • internal/api/udsapi/transport_parity_integration_test.go
  • internal/automation/dispatch_retry.go
  • internal/automation/dispatch_test.go
  • internal/cli/agent_identity.go
  • internal/cli/agent_identity_test.go
  • internal/cli/agent_kernel_metadata.go
  • internal/cli/agent_kernel_test.go
  • internal/cli/client_network.go
  • internal/cli/client_network_coordination.go
  • internal/cli/client_network_credentials.go
  • internal/cli/helpers_test.go
  • internal/cli/network.go
  • internal/cli/network_client_test.go
  • internal/cli/network_coordination_cmd.go
  • internal/cli/network_coordination_output.go
  • internal/cli/network_public_surfaces_test.go
  • internal/cli/network_test.go
  • internal/cli/session_event_output.go
  • internal/cli/session_test.go
  • internal/cli/spawn.go
  • internal/cli/workspace_resolution.go
  • internal/cli/workspace_test.go
  • internal/daemon/boot.go
  • internal/daemon/boot_hook_observers.go
  • internal/daemon/boot_memory_session.go
  • internal/daemon/boot_tool_registry.go
  • internal/daemon/boot_tool_registry_providers.go
  • internal/daemon/boot_workspace_access.go
  • internal/daemon/daemon_memory_e2e_integration_test.go
  • internal/daemon/daemon_mock_agents_integration_test.go
  • internal/daemon/daemon_test.go
  • internal/daemon/native_automation_tools_integration_test.go
  • internal/daemon/native_automation_tools_test.go
  • internal/daemon/native_bridge_catalog_tools.go
  • internal/daemon/native_bundle_resource_tools.go
  • internal/daemon/native_create_tools.go
  • internal/daemon/native_create_tools_test.go
  • internal/daemon/native_extension_tools_integration_test.go
  • internal/daemon/native_extension_tools_test.go
  • internal/daemon/native_goal_tools.go
  • internal/daemon/native_hook_helpers.go
  • internal/daemon/native_hook_query_tools.go
  • internal/daemon/native_loop_tools.go
  • internal/daemon/native_loop_tools_support.go
  • internal/daemon/native_loop_tools_test.go
  • internal/daemon/native_marketplace_tools_test.go
  • internal/daemon/native_session_list.go
  • internal/daemon/native_task_list.go
  • internal/daemon/native_tool_approval_grants_test.go
  • internal/daemon/native_tool_authored_paths.go
  • internal/daemon/native_tool_observe_helpers.go
  • internal/daemon/native_tool_registry_skills.go
  • internal/daemon/native_tool_scope.go
  • internal/daemon/native_tools_test.go
  • internal/daemon/native_workspace_input_authorizer.go
  • internal/daemon/native_workspace_input_binder.go
  • internal/daemon/prompt_sections.go
  • internal/daemon/runtime_dependencies.go
  • internal/daemon/runtime_deps.go
  • internal/daemon/server_options.go
  • internal/daemon/task_runtime_boot.go
  • internal/daemon/tool_approval_boot.go
  • internal/daemon/tool_approval_bridge_test.go
  • internal/daemon/tool_approval_grants_integration_test.go
  • internal/daemon/workspace_access_audit.go
  • internal/daemon/workspace_access_consent.go
  • internal/daemon/workspace_access_integration_test.go
  • internal/daemon/workspace_access_mode_source.go
  • internal/daemon/workspace_access_prompt_bridge.go
  • internal/daemon/workspace_access_test.go
  • internal/events/components.go
  • internal/events/registry_mcp_auth.go
  • internal/events/registry_test.go
  • internal/events/registry_workspace_access.go
  • internal/extension/host_api_integration_test.go
  • internal/extension/host_api_test.go
  • internal/extension/manager_integration_test.go
  • internal/mcp/hosted_proxy.go
  • internal/mcp/hosted_proxy_test.go
  • internal/mcp/hosted_proxy_transport_error.go
  • internal/session/manager_permissions_test.go
  • internal/session/manager_types.go
  • internal/session/manager_workspace_access.go
  • internal/session/query_metadata.go
  • internal/session/session.go
  • internal/session/session_info.go
  • internal/session/spawn.go
  • internal/session/spawn_permissions.go
  • internal/session/spawn_test.go
  • internal/store/globaldb/global_db_task_event_tx_test.go
  • internal/store/globaldb/global_db_test.go
  • internal/task/actors.go
  • internal/task/lease_claim_hooks.go
  • internal/task/lease_manager_claim.go
  • internal/task/lease_test.go
  • internal/task/manager.go
  • internal/task/manager_constructor.go
  • internal/task/manager_integration_test.go
  • internal/task/manager_test.go
  • internal/task/manager_workspace_access.go
  • internal/task/task_resource_authorizer.go
  • internal/testutil/e2e/runtime_harness_transport.go
  • internal/tools/dispatch.go
  • internal/tools/dispatch_authorization.go
  • internal/tools/dispatch_test.go
  • internal/tools/dispatch_workspace_access.go
  • internal/tools/reason.go
  • internal/tools/registry.go
  • internal/tools/tool.go
  • internal/workspace/coordination_commands.go
  • internal/workspace/coordination_commands_test.go
  • internal/workspace/discovery.go
  • internal/workspace/resolver_test.go
  • internal/workspaceaccess/audit.go
  • internal/workspaceaccess/default_policy.go
  • internal/workspaceaccess/default_policy_contracts.go
  • internal/workspaceaccess/default_policy_test.go
  • internal/workspaceaccess/default_policy_validation.go
  • internal/workspaceaccess/policy.go
  • magefiles/boundaries.go

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

This change adds policy-based cross-workspace authorization across daemon, APIs, CLI, task and tool services, coordination endpoints, session spawning, and web session deep links. It also adds session-owner projections, consent and audit handling, workspace canonicalization, and extensive unit and integration coverage.

Changes

Workspace access policy

Layer / File(s) Summary
Policy contracts and enforcement
internal/workspaceaccess/*, internal/task/*, internal/workspace/coordination_commands.go
Defines authorization requests, decisions, permission modes, consent, auditing, and policy-based task and coordination authorization.
Daemon integration
internal/daemon/*
Bootstraps the policy, session consent cache, audit emitter, prompt bridge, and native tool input authorization.
API and CLI wiring
internal/api/*, internal/cli/*
Propagates workspace policies and agent credentials through HTTP, UDS, CLI, network coordination, spawning, and workspace resolution flows.
Web routing
web/src/routes/*, web/src/systems/session/*, web/src/systems/os/lib/routing-coordinator.ts
Adds foreign-session owner lookup, workspace-switch confirmation, route holding, and deferred route reconciliation.
Supporting coverage and contracts
internal/**/**_test.go, web/src/**/__tests__/*, internal/api/contract/*
Adds session-owner contracts, authorization tests, consent/audit integration tests, transport parity checks, and cross-workspace behavior coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant API
  participant WorkspaceAccessPolicy
  participant TaskService
  participant AuditStore
  Agent->>API: Submit workspace-scoped request
  API->>WorkspaceAccessPolicy: Authorize(actor, target, seam)
  WorkspaceAccessPolicy->>AuditStore: Emit access decision
  WorkspaceAccessPolicy-->>API: Allowed or denied decision
  API->>TaskService: Create or claim task
  TaskService-->>Agent: Response
Loading

Possibly related PRs

  • compozy/compozy#109 — Adds CLI task runtime flag support on related task runtime infrastructure.
  • compozy/compozy#266 — Previously modified the workspace validation flow in internal/agentidentity/identity.go.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main cross-workspace access work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch cross-workspaces
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cross-workspaces

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/api/core/agent_handlers.go (1)

70-76: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Normalize workspace-origin entry IDs in the shared resolver.

ListAgents and workspaceAgentDef now patch WorkspaceID per caller, but ListAgentCatalog transforms resolved.Entries directly. Stamp resolved.WorkspaceID onto workspace-origin entries here, then remove the duplicate caller-side patches.

🤖 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 `@internal/api/core/agent_handlers.go` around lines 70 - 76, Update the shared
resolver around ListAgentCatalog to assign resolved.WorkspaceID to each
workspace-origin entry before transforming resolved.Entries into agent
definitions. Remove the duplicate WorkspaceID patching from ListAgents and
workspaceAgentDef, leaving those callers to consume the normalized entries.
internal/daemon/native_workspace_input_binder.go (1)

170-182: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Bind and authorize native workspace inputs with the stable workspace ID.

bindNativeWorkspaceField writes resolvedWorkspace.ID to the bound payload, but ResolvedWorkspace.WorkspaceID is the stable workspace identity and tests separate these namespaces. authorizeWorkspaceTarget compares the bound target against scope.WorkspaceID from the caller/workspace source, which also uses that stable ID in existing coverage. Compare/assign the stable ID there to avoid denying own-workspace calls when the two IDs diverge.

🤖 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 `@internal/daemon/native_workspace_input_binder.go` around lines 170 - 182,
Update bindNativeWorkspaceField to compare and assign
ResolvedWorkspace.WorkspaceID instead of ResolvedWorkspace.ID, matching
authorizeWorkspaceTarget and the caller scope’s stable workspace identity.
Preserve the existing requested-versus-trusted selection and error handling.
🧹 Nitpick comments (14)
internal/agentidentity/identity.go (1)

172-177: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Preserve the policy error instead of swallowing it.

policy.Authorize failures (store/transport errors, cancelled context) are indistinguishable from real denials here, and the returned hint tells the operator to change permissions.mode even when the cause was infrastructural. Join the underlying error so callers and logs keep the root cause while the deny decision stays fail-closed.

♻️ Suggested change
-	if policy != nil {
-		decision, err := policy.Authorize(ctx, req)
-		if err == nil && decision.Allowed {
-			return nil
-		}
-	}
-	return identityError(
-		ErrIdentityUnauthorized,
-		"identity_unauthorized",
-		"agent session does not belong to the requested workspace",
-		workspaceaccess.DenialHint,
-	)
+	var authorizeErr error
+	if policy != nil {
+		decision, err := policy.Authorize(ctx, req)
+		if err == nil && decision.Allowed {
+			return nil
+		}
+		authorizeErr = err
+	}
+	return identityError(
+		errors.Join(ErrIdentityUnauthorized, authorizeErr),
+		"identity_unauthorized",
+		"agent session does not belong to the requested workspace",
+		workspaceaccess.DenialHint,
+	)

Note: errors.Join(err, nil) returns just err, so the allowed path and the plain-denial path stay unchanged.

🤖 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 `@internal/agentidentity/identity.go` around lines 172 - 177, Update the policy
authorization handling around policy.Authorize in the identity flow to preserve
any non-nil authorization error while still returning a fail-closed denial. Join
the underlying error with the existing denial error, ensuring allowed requests
and plain policy denials retain their current behavior.

Source: Coding guidelines

internal/daemon/daemon_mock_agents_integration_test.go (1)

1438-1457: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

requireLatestHostedMCPStdioServer duplicates requireHostedMCPStdioServer except for iteration order.

Consider a single helper with a direction flag (or one that always returns the latest, if earliest-match had no semantic requirement) to avoid the two copies drifting.

🤖 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 `@internal/daemon/daemon_mock_agents_integration_test.go` around lines 1438 -
1457, Consolidate requireLatestHostedMCPStdioServer with the existing
requireHostedMCPStdioServer helper instead of maintaining duplicate MCP
server-search logic. Add a direction or latest-selection option only if the
existing helper must preserve earliest-match behavior, while retaining the
current failure message and latest-session semantics.
internal/task/task_resource_authorizer.go (1)

59-79: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Policy authorization errors are silently swallowed.

taskWorkspaceAccessAllowed discards the error from policy.Authorize and only returns a boolean. Fail-closed-on-error is the right default for security, but the actual error (e.g., a misconfigured or unreachable policy backend) is never logged, making it indistinguishable from a legitimate deny when debugging denied cross-workspace task access.

As per path instructions for internal/**/*.go, error handling and logging conventions should be followed when editing production Go under internal/.

♻️ Suggested logging addition
 func taskWorkspaceAccessAllowed(
 	ctx context.Context,
 	policy workspaceaccess.Policy,
 	actor ActorContext,
 	targetWorkspaceID string,
 ) bool {
 	if policy == nil {
 		return false
 	}
 	decision, err := policy.Authorize(ctx, workspaceaccess.Request{
 		Actor: workspaceaccess.ActorRef{
 			Kind:        workspaceaccess.ActorKind(actor.Actor.Kind.Normalize()),
 			SessionID:   strings.TrimSpace(actor.Scope.SessionID),
 			WorkspaceID: strings.TrimSpace(actor.Scope.WorkspaceID),
 			Operator:    actor.Scope.Operator,
 		},
 		TargetWorkspaceID: strings.TrimSpace(targetWorkspaceID),
 		Seam:              workspaceaccess.SeamTask,
 	})
+	if err != nil {
+		logger.FromContext(ctx).Warn("workspace access policy authorization failed", "error", err)
+		return false
+	}
 	return err == nil && decision.Allowed
 }
🤖 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 `@internal/task/task_resource_authorizer.go` around lines 59 - 79, Update
taskWorkspaceAccessAllowed to log the error returned by policy.Authorize before
preserving the existing fail-closed behavior of returning false. Follow the
repository’s established error-logging convention for internal Go code and keep
legitimate denied decisions returning false without logging an error.

Source: Path instructions

web/src/systems/session/lib/session-owner-query.ts (1)

14-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Query key/options factory not co-located in lib/query-options.ts.

sessionOwnerKeys/sessionOwnerOptions are defined in a new session-owner-query.ts file rather than in lib/query-options.ts. The in-code rationale (keeping the owner projection out of sessionKeys per ADR-004 to avoid workspace-cache leakage) is sound, but the same isolation could be achieved by exporting a separate key/options factory from lib/query-options.ts itself, satisfying the co-location guideline while preserving the namespace separation.

As per coding guidelines, "Co-locate query keys and query functions in lib/query-options.ts; reuse option/key factories... and reconcile mutations through canonical keys."

🤖 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 `@web/src/systems/session/lib/session-owner-query.ts` around lines 14 - 35, The
session-owner query keys and options are in a separate session-owner-query
module instead of the shared query-options module. Move the sessionOwnerKeys,
SessionOwnerDialogState, and sessionOwnerOptions definitions into
lib/query-options.ts, preserving their separate namespace from sessionKeys and
keeping sessionOwnerOptions’ query key, fetchSessionOwner query function, stale
time, and enabled behavior unchanged; update imports and usages accordingly.

Source: Coding guidelines

internal/daemon/native_workspace_input_binder.go (1)

248-250: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Preserve the policy error cause in the denial.

err from Authorize is dropped and replaced with a generic denial, so infrastructure failures (mode source unavailable, audit store down) become indistinguishable from genuine policy denials during triage.

♻️ Wrap the cause
 	if err != nil {
-		return nativeWorkspaceAccessDeniedError(descriptor.ID)
+		return fmt.Errorf("%w: authorize workspace %q: %w",
+			nativeWorkspaceAccessDeniedError(descriptor.ID), targetWorkspaceID, err)
 	}
🤖 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 `@internal/daemon/native_workspace_input_binder.go` around lines 248 - 250,
Update the Authorize error path in the native workspace input binding flow to
preserve err when constructing the denial, wrapping the original cause while
retaining the nativeWorkspaceAccessDeniedError context. Ensure infrastructure
failures remain distinguishable from genuine policy denials and keep the
existing successful authorization path unchanged.
internal/daemon/native_tools_test.go (1)

1095-1103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These assertions no longer test authorization; the messages claim otherwise.

adapter is built with only Workspaces — no binder, no policy — so resolving ws-2 from a ws-1 scope succeeds simply because nativeCallerWorkspaceInput no longer rejects foreign refs. The failure messages ("want binder-authorized ws-2") suggest a binder authorized the call, which is misleading for future readers. Consider renaming to reflect that the raw adapter defers authorization to the registry seam.

♻️ Clarify intent
 		if err != nil || foreignResolved.WorkspaceID != "ws-2" {
-			t.Fatalf("nativeResolvedWorkspace(foreign) = %#v, %v, want binder-authorized ws-2", foreignResolved, err)
+			t.Fatalf(
+				"nativeResolvedWorkspace(foreign) = %#v, %v, want ws-2 resolved (authorization deferred to the registry binder)",
+				foreignResolved,
+				err,
+			)
 		}

Also applies to: 1146-1153

🤖 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 `@internal/daemon/native_tools_test.go` around lines 1095 - 1103, Rename the
assertions around adapter.nativeResolvedWorkspace in the affected test cases to
reflect raw adapter resolution and registry-seam authorization, not binder
authorization. Update the failure messages to remove “binder-authorized”
language while preserving the expected ws-2 result and existing assertion
behavior.
internal/daemon/workspace_access_audit.go (1)

67-67: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Detached audit write has no deadline and runs synchronously on the authorization path.

context.WithoutCancel(ctx) correctly prevents request cancellation from dropping the ledger append, but it also removes the caller's deadline. Since DefaultPolicy.Authorize emits synchronously (internal/workspaceaccess/default_policy.go Lines 43-61), a wedged or lock-contended event store blocks every workspace-access decision indefinitely. Reattach a bounded deadline.

🛡️ Reattach a deadline
-	if err := e.store.WriteEventSummary(context.WithoutCancel(ctx), store.EventSummary{
+	writeCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), workspaceAccessAuditWriteTimeout)
+	defer cancel()
+	if err := e.store.WriteEventSummary(writeCtx, store.EventSummary{

Plus a package-level constant:

const workspaceAccessAuditWriteTimeout = 5 * time.Second

As per coding guidelines, "Work that outlives an HTTP/UDS request must use context.WithoutCancel(ctx) ...; reattach a deadline when needed."

🤖 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 `@internal/daemon/workspace_access_audit.go` at line 67, Update the audit write
in the surrounding workspace access audit function to derive a context with
WithoutCancel(ctx) and apply a five-second timeout using the package-level
workspaceAccessAuditWriteTimeout constant before calling WriteEventSummary.
Preserve the detached context behavior while ensuring the synchronous
authorization path cannot block indefinitely, and release the derived context
when the write completes.

Source: Coding guidelines

internal/daemon/workspace_access_prompt_bridge.go (1)

107-109: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Underlying outcome.Validate() error is discarded from the returned message.

The error from outcome.Validate() is checked but its details are dropped in favor of a generic message. Wrapping it would aid debugging malformed ACP outcomes.

♻️ Optional improvement
-	if err := outcome.Validate(); err != nil || outcome.Selected == nil {
-		return false, errors.New("daemon: workspace access prompt returned no selected outcome")
-	}
+	if err := outcome.Validate(); err != nil {
+		return false, fmt.Errorf("daemon: workspace access prompt outcome invalid: %w", err)
+	}
+	if outcome.Selected == nil {
+		return false, errors.New("daemon: workspace access prompt returned no selected outcome")
+	}
🤖 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 `@internal/daemon/workspace_access_prompt_bridge.go` around lines 107 - 109,
Update the validation branch in the workspace access prompt bridge to preserve
and wrap the error returned by outcome.Validate() when validation fails, while
retaining the existing generic error for a nil outcome.Selected case. Ensure
malformed ACP outcome details are included in the returned error message.
web/src/systems/os/components/os-route-hold.tsx (1)

12-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move route coordination out of the component layer.

OsRouteHold is route orchestration, not a presentational component. Move this effect into the decision route or a route-level hook.

As per coding guidelines, “UI components must be pure and presentational; orchestration and data fetching belong at route/page level.”

🤖 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 `@web/src/systems/os/components/os-route-hold.tsx` around lines 12 - 20, Move
the route-hold orchestration from OsRouteHold into the decision route or an
equivalent route-level hook, including the useLocation values and coordinator
hold/release effect. Remove the component-layer effect and ensure the route
still holds on mount or route changes and releases on cleanup.

Source: Coding guidelines

web/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsx (2)

240-256: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Harness components are declared after builder's return.

Hoisting makes this valid, but the returned object followed by unreachable-looking declarations is easy to misread. Moving the harness components above the return (or to module scope, parameterized by the route handles) would read better.

🤖 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
`@web/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsx`
around lines 240 - 256, Move the AgentsFleetRouteHarness and
AgentDetailRouteHarness declarations above the builder’s return statement,
keeping their existing behavior and route-handle references unchanged.

306-309: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the exported owner/context types instead of restating them inline.

{ permalinkError?: string; sessionOwner?: { sessionId; workspaceId; workspaceName } } duplicates SessionPermalinkRouteContext / SessionOwnerDialogState. Importing those types keeps the harness failing (at typecheck) if the production projection changes shape.

♻️ Suggested change
-    const context = permalinkRoute.useRouteContext() as {
-      permalinkError?: string;
-      sessionOwner?: { sessionId: string; workspaceId: string; workspaceName: string };
-    };
+    const context = permalinkRoute.useRouteContext() as Pick<
+      SessionPermalinkRouteContext,
+      "permalinkError" | "sessionOwner"
+    >;
🤖 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
`@web/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsx`
around lines 306 - 309, Update the route-context typing near
permalinkRoute.useRouteContext to import and reuse the exported
SessionPermalinkRouteContext and SessionOwnerDialogState types instead of
duplicating the permalinkError and sessionOwner structure inline. Keep the test
context aligned with the production route projection so type changes are
detected automatically.
web/src/routes/_app/session.$id.tsx (1)

40-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Dialog wiring duplicated with the session leaf route.

This OsRouteHold + SessionWorkspaceSwitchDialog + confirm/cancel navigation block is byte-for-byte the same shape as web/src/routes/_app/agents.$name.sessions.$id.tsx (lines 59-80) apart from the to target. A small composite (owner + re-enter target) in @/systems/session would keep both seams from diverging as the confirmation flow evolves.

🤖 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 `@web/src/routes/_app/session`.$id.tsx around lines 40 - 56, The session
workspace-switch confirmation UI is duplicated between the session route and
agent session route. Extract a shared composite in the session system that
accepts the owner and re-entry navigation target, encapsulates OsRouteHold,
SessionWorkspaceSwitchDialog, and confirm/cancel navigation, then replace both
route-local blocks with that component while preserving each route’s existing
target.
internal/session/spawn_test.go (1)

96-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider moving this subtest out of TestValidatePermissionSubset.

The scenario exercises Manager.Spawn cross-workspace authorization (policy invocation + workspace binding + per-workspace cap), not ValidatePermissionSubset. Hosting it in a dedicated top-level test (e.g. TestSpawnWorkspaceAccess) keeps the invariant and owning suite aligned and makes failures easier to attribute.

Also, policy.calls != 2 pins an internal prepareSpawn detail; asserting the recorded requests plus a non-zero call count would be less brittle.

As per path instructions, "Before creating tests, identify the invariant, owning layer, and canonical suite; extend the existing suite rather than creating duplicate standalone regressions."

🤖 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 `@internal/session/spawn_test.go` around lines 96 - 157, Move the
cross-workspace scenario out of TestValidatePermissionSubset into the dedicated
top-level Manager.Spawn workspace-access suite, such as
TestSpawnWorkspaceAccess, preserving assertions for authorization, workspace
binding, and per-workspace limits. Replace the exact policy.calls == 2 assertion
with a non-zero call-count check while retaining validation of every recorded
request against wantRequest.

Source: Path instructions

web/src/routes/_app/-agent-session-route-loader.ts (1)

53-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Foreign-session resolution rule implemented twice. Both deep-link seams re-implement the same policy — cached foreign owner check, active-workspace detail miss, owner projection lookup, compare owner workspace against the active one — differing only in how a miss is signalled. One shared helper in @/systems/session keeps the rule (and its "owner projection only, never foreign detail before confirm" invariant) in a single place.

  • web/src/routes/_app/-agent-session-route-loader.ts#L53-L69: replace resolveForeignSession with the shared helper, mapping a miss to { status: "not-found" }.
  • web/src/routes/_app/-session-permalink-route.ts#L117-L126: replace resolveForeignPermalink with the same helper, throwing SessionNotFoundError on a miss.
🤖 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 `@web/src/routes/_app/-agent-session-route-loader.ts` around lines 53 - 69,
Replace resolveForeignSession in
web/src/routes/_app/-agent-session-route-loader.ts lines 53-69 with the shared
`@/systems/session` helper, mapping a miss to { status: "not-found" }. Replace
resolveForeignPermalink in web/src/routes/_app/-session-permalink-route.ts lines
117-126 with the same helper, throwing SessionNotFoundError on a miss; preserve
the owner-projection-only invariant.
🤖 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 `@internal/api/core/tool_errors.go`:
- Around line 119-121: The workspace-access-denied branch in
internal/api/core/tool_errors.go (lines 119-121) must stop forwarding
ToolError.Message and instead return a stable vetted public denial message.
Update the related assertions in internal/api/core/tools_test.go (lines 300-316)
to expect the canonical sanitized message and verify that a secret-bearing
internal message is not present.

In `@internal/api/udsapi/routes.go`:
- Line 357: Apply handlers.AuthorizeNetworkWorkspaceAccess to the
/network-coordination route group as well as workspaceNetwork, ensuring its read
and mutation endpoints enforce the cross-workspace check. Add a UDS test
covering a denied agent accessing a coordination endpoint and verify the request
is rejected.

In `@internal/cli/client_network_credentials.go`:
- Around line 33-49: Update newNetworkStatusCommand so NetworkStatus receives
the workspace-scoped credentials established after resolveNetworkWorkspaceRef,
rather than calling client.NetworkStatus directly with the unwrapped command
context. Reuse the existing credential setup path used by doNetworkJSON or pass
the wrapped context into NetworkStatus, preserving the current status request
behavior.

In `@internal/cli/workspace_test.go`:
- Around line 483-496: Extend the assertions in the workspace resolution test
around executeRootCommand and decoded workspaceDetailOutput to verify that
decoded.Workspace.ID equals "ws-foreign" for the cross-workspace cases. Keep the
existing ResolutionSource assertion and ensure the test validates the selected
workspace outcome, not only its resolution source.

In `@internal/daemon/daemon_mock_agents_integration_test.go`:
- Around line 1058-1089: Strengthen the “Should consume allow-once consent only
for one call” subtest so it verifies the first grant is consumed rather than
approving both calls explicitly. After the initial call through
callWorkspaceAccessToolWithDecision, make the second call via the bare
callWorkspaceAccessTool or otherwise assert that it triggers a new
prompt/denial, including the relevant permission-event or message validation so
the test fails if consent becomes session-wide.

In `@internal/daemon/native_workspace_input_binder.go`:
- Around line 264-273: Stop fail-open authorization in
nativeWorkspaceScopeHasOperatorAuthority and the session-status path: require
explicit scope.Operator or a dedicated internal-actor kind instead of treating
nativeWorkspaceScopeUnbound as authoritative, and make nil sessions or Status
failures return an error/sentinel that causes authorizeWorkspaceTarget to deny
rather than trust scope.WorkspaceID or scope.AgentName. Apply these changes at
internal/daemon/native_workspace_input_binder.go lines 264-273 (anchor) and
285-296 (sibling).

In `@internal/session/spawn.go`:
- Around line 256-260: Update the spawn authorization flow around
resolver.Resolve and policy.Authorize to preserve validation/backend failures as
actionable errors rather than converting them to spawnWorkspaceAccessDenied.
Return a validation error for workspace-ref resolution failures, matching the
existing parent-workspace resolution handling, and propagate or wrap
authorizeErr when policy evaluation fails. Keep spawnWorkspaceAccessDenied only
for successfully resolved references that policy explicitly denies.

In `@internal/task/manager_integration_test.go`:
- Around line 414-452: The test must exercise automation authorization while a
run remains claimable. Update the flow around the agent and automation calls in
the integration test to either perform the automation claim first or enqueue a
separate queued run for it, then assert ErrPermissionDenied and the
corresponding policy request before verifying the agent claim and its propagated
request.

In `@internal/tools/dispatch_test.go`:
- Around line 746-760: Extend the denial-path assertions after the second
registry.Call in the test to verify policy authorization was consulted: assert
policy.calls == 2 and validate the canonical authorization request shape using
the policy’s recorded request data. Keep the existing
ReasonWorkspaceAccessDenied and provider-not-called assertions.

In `@web/src/systems/os/lib/routing-coordinator.ts`:
- Around line 319-330: Update startRouteReconciliation to clear the matching
routeReconciliation and call reportAuthoritativeState when outcome.accepted is
false, rather than returning with pending state. Also handle rejected
outcome.completion promises with the same token-guarded cleanup and
authoritative-state reporting used for successful completion, while preserving
protection against clearing a newer reconciliation.

---

Outside diff comments:
In `@internal/api/core/agent_handlers.go`:
- Around line 70-76: Update the shared resolver around ListAgentCatalog to
assign resolved.WorkspaceID to each workspace-origin entry before transforming
resolved.Entries into agent definitions. Remove the duplicate WorkspaceID
patching from ListAgents and workspaceAgentDef, leaving those callers to consume
the normalized entries.

In `@internal/daemon/native_workspace_input_binder.go`:
- Around line 170-182: Update bindNativeWorkspaceField to compare and assign
ResolvedWorkspace.WorkspaceID instead of ResolvedWorkspace.ID, matching
authorizeWorkspaceTarget and the caller scope’s stable workspace identity.
Preserve the existing requested-versus-trusted selection and error handling.

---

Nitpick comments:
In `@internal/agentidentity/identity.go`:
- Around line 172-177: Update the policy authorization handling around
policy.Authorize in the identity flow to preserve any non-nil authorization
error while still returning a fail-closed denial. Join the underlying error with
the existing denial error, ensuring allowed requests and plain policy denials
retain their current behavior.

In `@internal/daemon/daemon_mock_agents_integration_test.go`:
- Around line 1438-1457: Consolidate requireLatestHostedMCPStdioServer with the
existing requireHostedMCPStdioServer helper instead of maintaining duplicate MCP
server-search logic. Add a direction or latest-selection option only if the
existing helper must preserve earliest-match behavior, while retaining the
current failure message and latest-session semantics.

In `@internal/daemon/native_tools_test.go`:
- Around line 1095-1103: Rename the assertions around
adapter.nativeResolvedWorkspace in the affected test cases to reflect raw
adapter resolution and registry-seam authorization, not binder authorization.
Update the failure messages to remove “binder-authorized” language while
preserving the expected ws-2 result and existing assertion behavior.

In `@internal/daemon/native_workspace_input_binder.go`:
- Around line 248-250: Update the Authorize error path in the native workspace
input binding flow to preserve err when constructing the denial, wrapping the
original cause while retaining the nativeWorkspaceAccessDeniedError context.
Ensure infrastructure failures remain distinguishable from genuine policy
denials and keep the existing successful authorization path unchanged.

In `@internal/daemon/workspace_access_audit.go`:
- Line 67: Update the audit write in the surrounding workspace access audit
function to derive a context with WithoutCancel(ctx) and apply a five-second
timeout using the package-level workspaceAccessAuditWriteTimeout constant before
calling WriteEventSummary. Preserve the detached context behavior while ensuring
the synchronous authorization path cannot block indefinitely, and release the
derived context when the write completes.

In `@internal/daemon/workspace_access_prompt_bridge.go`:
- Around line 107-109: Update the validation branch in the workspace access
prompt bridge to preserve and wrap the error returned by outcome.Validate() when
validation fails, while retaining the existing generic error for a nil
outcome.Selected case. Ensure malformed ACP outcome details are included in the
returned error message.

In `@internal/session/spawn_test.go`:
- Around line 96-157: Move the cross-workspace scenario out of
TestValidatePermissionSubset into the dedicated top-level Manager.Spawn
workspace-access suite, such as TestSpawnWorkspaceAccess, preserving assertions
for authorization, workspace binding, and per-workspace limits. Replace the
exact policy.calls == 2 assertion with a non-zero call-count check while
retaining validation of every recorded request against wantRequest.

In `@internal/task/task_resource_authorizer.go`:
- Around line 59-79: Update taskWorkspaceAccessAllowed to log the error returned
by policy.Authorize before preserving the existing fail-closed behavior of
returning false. Follow the repository’s established error-logging convention
for internal Go code and keep legitimate denied decisions returning false
without logging an error.

In
`@web/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsx`:
- Around line 240-256: Move the AgentsFleetRouteHarness and
AgentDetailRouteHarness declarations above the builder’s return statement,
keeping their existing behavior and route-handle references unchanged.
- Around line 306-309: Update the route-context typing near
permalinkRoute.useRouteContext to import and reuse the exported
SessionPermalinkRouteContext and SessionOwnerDialogState types instead of
duplicating the permalinkError and sessionOwner structure inline. Keep the test
context aligned with the production route projection so type changes are
detected automatically.

In `@web/src/routes/_app/-agent-session-route-loader.ts`:
- Around line 53-69: Replace resolveForeignSession in
web/src/routes/_app/-agent-session-route-loader.ts lines 53-69 with the shared
`@/systems/session` helper, mapping a miss to { status: "not-found" }. Replace
resolveForeignPermalink in web/src/routes/_app/-session-permalink-route.ts lines
117-126 with the same helper, throwing SessionNotFoundError on a miss; preserve
the owner-projection-only invariant.

In `@web/src/routes/_app/session`.$id.tsx:
- Around line 40-56: The session workspace-switch confirmation UI is duplicated
between the session route and agent session route. Extract a shared composite in
the session system that accepts the owner and re-entry navigation target,
encapsulates OsRouteHold, SessionWorkspaceSwitchDialog, and confirm/cancel
navigation, then replace both route-local blocks with that component while
preserving each route’s existing target.

In `@web/src/systems/os/components/os-route-hold.tsx`:
- Around line 12-20: Move the route-hold orchestration from OsRouteHold into the
decision route or an equivalent route-level hook, including the useLocation
values and coordinator hold/release effect. Remove the component-layer effect
and ensure the route still holds on mount or route changes and releases on
cleanup.

In `@web/src/systems/session/lib/session-owner-query.ts`:
- Around line 14-35: The session-owner query keys and options are in a separate
session-owner-query module instead of the shared query-options module. Move the
sessionOwnerKeys, SessionOwnerDialogState, and sessionOwnerOptions definitions
into lib/query-options.ts, preserving their separate namespace from sessionKeys
and keeping sessionOwnerOptions’ query key, fetchSessionOwner query function,
stale time, and enabled behavior unchanged; update imports and usages
accordingly.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ed53d45-af72-4460-be06-92d82c3adaef

📥 Commits

Reviewing files that changed from the base of the PR and between c36e31b and a2ccd05.

⛔ Files ignored due to path filters (40)
  • docs/_memory/glossary.md is excluded by !**/*.md
  • docs/qa/bugs/BUG-0028.md is excluded by !**/*.md
  • docs/qa/bugs/BUG-20260719-autonomous-progress-unobservable.md is excluded by !**/*.md
  • docs/qa/bugs/BUG-20260729-coordination-cli-drops-agent-identity.md is excluded by !**/*.md
  • docs/qa/bugs/BUG-20260729-nearest-workspace-case-alias.md is excluded by !**/*.md
  • docs/qa/charters/CH-cross-workspace-consent-audit.md is excluded by !**/*.md
  • docs/qa/charters/CH-cross-workspace-mode-seams.md is excluded by !**/*.md
  • docs/qa/charters/CH-foreign-session-deep-link.md is excluded by !**/*.md
  • docs/qa/charters/CH-workspace-binding-canary.md is excluded by !**/*.md
  • docs/qa/journeys/J-11-return-to-running-session.md is excluded by !**/*.md
  • docs/qa/journeys/J-12-open-session-fast.md is excluded by !**/*.md
  • docs/qa/journeys/J-cross-workspace-access.md is excluded by !**/*.md
  • docs/qa/journeys/J-open-foreign-session.md is excluded by !**/*.md
  • docs/qa/journeys/J-operate-workspace-context.md is excluded by !**/*.md
  • docs/qa/reports/2026-07-29-cross-workspace-access.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-native-workspace-scope-isolation.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-web-session-cross-workspace-confirm.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-web-session-deep-link-isolation.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-workspace-access-mode-matrix.md is excluded by !**/*.md
  • docs/qa/scenarios/ET-workspace-access-prompt-outcomes.md is excluded by !**/*.md
  • docs/qa/scenarios/MS-workspace-resolution-chain.md is excluded by !**/*.md
  • docs/qa/scenarios/RT-073.md is excluded by !**/*.md
  • docs/qa/scenarios/TA-053.md is excluded by !**/*.md
  • internal/testutil/acpmock/testdata/workspace_access_fixture.json is excluded by !**/*.json
  • openapi/compozy.json is excluded by !**/*.json
  • packages/site/content/runtime/cli-reference/spawn.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/agents/spawning.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/autonomy/safe-spawn.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/configuration/config-toml.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/hooks/event-catalog.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/sessions/permissions.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/workspaces/index.mdx is excluded by !**/*.mdx
  • packages/site/content/runtime/core/workspaces/resolver.mdx is excluded by !**/*.mdx
  • skills/compozy/references/agent-definitions.md is excluded by !**/*.md
  • skills/compozy/references/native-tools.md is excluded by !**/*.md
  • web/e2e/__tests__/os-shell.spec.ts is excluded by !web/e2e/**
  • web/e2e/__tests__/settings-transport.spec.ts is excluded by !web/e2e/**
  • web/e2e/fixtures/selectors.ts is excluded by !**/fixtures/**, !web/e2e/**
  • web/src/generated/compozy-openapi.d.ts is excluded by !**/generated/**, !**/generated/**, !**/*.d.ts
  • web/src/systems/session/routes/session-permalink.stories.tsx is excluded by !**/*.stories.tsx
📒 Files selected for processing (178)
  • internal/agentidentity/identity.go
  • internal/agentidentity/identity_test.go
  • internal/api/contract/agents.go
  • internal/api/contract/session_owner.go
  • internal/api/core/agent_channels_internal_test.go
  • internal/api/core/agent_definition_resolution.go
  • internal/api/core/agent_fleet_catalog.go
  • internal/api/core/agent_handlers.go
  • internal/api/core/agent_identity.go
  • internal/api/core/agent_spawn.go
  • internal/api/core/agent_spawn_test.go
  • internal/api/core/agent_task_claim_criteria.go
  • internal/api/core/base_handlers.go
  • internal/api/core/handlers_agents.go
  • internal/api/core/handlers_test.go
  • internal/api/core/network_coordination_public_test.go
  • internal/api/core/network_workspace_access.go
  • internal/api/core/session_owner.go
  • internal/api/core/tasks_internal_test.go
  • internal/api/core/test_helpers_test.go
  • internal/api/core/tool_errors.go
  • internal/api/core/tools_test.go
  • internal/api/httpapi/agent_context_test.go
  • internal/api/httpapi/handlers.go
  • internal/api/httpapi/handlers_test.go
  • internal/api/httpapi/routes.go
  • internal/api/httpapi/server.go
  • internal/api/httpapi/server_handler_config.go
  • internal/api/httpapi/session_routes.go
  • internal/api/spec/registry_sessions.go
  • internal/api/testutil/workspace_access_stub.go
  • internal/api/udsapi/agent_identity_test.go
  • internal/api/udsapi/handler_config.go
  • internal/api/udsapi/handlers_test.go
  • internal/api/udsapi/routes.go
  • internal/api/udsapi/server.go
  • internal/api/udsapi/server_handler_config.go
  • internal/api/udsapi/server_handlers.go
  • internal/api/udsapi/session_routes.go
  • internal/api/udsapi/transport_parity_integration_test.go
  • internal/automation/dispatch_retry.go
  • internal/automation/dispatch_test.go
  • internal/cli/agent_identity.go
  • internal/cli/agent_identity_test.go
  • internal/cli/agent_kernel_metadata.go
  • internal/cli/agent_kernel_test.go
  • internal/cli/client_network.go
  • internal/cli/client_network_coordination.go
  • internal/cli/client_network_credentials.go
  • internal/cli/helpers_test.go
  • internal/cli/network.go
  • internal/cli/network_client_test.go
  • internal/cli/network_coordination_cmd.go
  • internal/cli/network_coordination_output.go
  • internal/cli/network_public_surfaces_test.go
  • internal/cli/network_test.go
  • internal/cli/session_event_output.go
  • internal/cli/session_test.go
  • internal/cli/spawn.go
  • internal/cli/workspace_resolution.go
  • internal/cli/workspace_test.go
  • internal/daemon/boot.go
  • internal/daemon/boot_hook_observers.go
  • internal/daemon/boot_memory_session.go
  • internal/daemon/boot_tool_registry.go
  • internal/daemon/boot_tool_registry_providers.go
  • internal/daemon/boot_workspace_access.go
  • internal/daemon/daemon_mock_agents_integration_test.go
  • internal/daemon/daemon_review_and_fix_e2e_integration_test.go
  • internal/daemon/daemon_software_delivery_e2e_integration_test.go
  • internal/daemon/daemon_test.go
  • internal/daemon/native_automation_tools_test.go
  • internal/daemon/native_bridge_catalog_tools.go
  • internal/daemon/native_bundle_resource_tools.go
  • internal/daemon/native_create_tools.go
  • internal/daemon/native_hook_helpers.go
  • internal/daemon/native_hook_query_tools.go
  • internal/daemon/native_loop_tools_support.go
  • internal/daemon/native_loop_tools_test.go
  • internal/daemon/native_session_list.go
  • internal/daemon/native_task_list.go
  • internal/daemon/native_tool_approval_grants_test.go
  • internal/daemon/native_tool_authored_paths.go
  • internal/daemon/native_tool_observe_helpers.go
  • internal/daemon/native_tool_registry_skills.go
  • internal/daemon/native_tool_scope.go
  • internal/daemon/native_tools_test.go
  • internal/daemon/native_workspace_input_binder.go
  • internal/daemon/prompt_sections.go
  • internal/daemon/runtime_dependencies.go
  • internal/daemon/runtime_deps.go
  • internal/daemon/server_options.go
  • internal/daemon/task_runtime_boot.go
  • internal/daemon/tool_approval_boot.go
  • internal/daemon/tool_approval_bridge_test.go
  • internal/daemon/tool_approval_grants_integration_test.go
  • internal/daemon/workspace_access_audit.go
  • internal/daemon/workspace_access_consent.go
  • internal/daemon/workspace_access_integration_test.go
  • internal/daemon/workspace_access_mode_source.go
  • internal/daemon/workspace_access_prompt_bridge.go
  • internal/daemon/workspace_access_test.go
  • internal/events/components.go
  • internal/events/registry_mcp_auth.go
  • internal/events/registry_test.go
  • internal/events/registry_workspace_access.go
  • internal/extension/host_api_integration_test.go
  • internal/extension/host_api_test.go
  • internal/extension/manager_integration_test.go
  • internal/mcp/hosted_proxy.go
  • internal/mcp/hosted_proxy_test.go
  • internal/mcp/hosted_proxy_transport_error.go
  • internal/session/manager_permissions_test.go
  • internal/session/manager_types.go
  • internal/session/manager_workspace_access.go
  • internal/session/query_metadata.go
  • internal/session/session.go
  • internal/session/session_info.go
  • internal/session/spawn.go
  • internal/session/spawn_permissions.go
  • internal/session/spawn_test.go
  • internal/store/globaldb/global_db_task_event_tx_test.go
  • internal/store/globaldb/global_db_test.go
  • internal/task/actors.go
  • internal/task/lease_claim_hooks.go
  • internal/task/lease_manager_claim.go
  • internal/task/lease_test.go
  • internal/task/manager.go
  • internal/task/manager_constructor.go
  • internal/task/manager_integration_test.go
  • internal/task/manager_test.go
  • internal/task/manager_workspace_access.go
  • internal/task/task_resource_authorizer.go
  • internal/testutil/e2e/runtime_harness_transport.go
  • internal/tools/dispatch.go
  • internal/tools/dispatch_authorization.go
  • internal/tools/dispatch_test.go
  • internal/tools/dispatch_workspace_access.go
  • internal/tools/reason.go
  • internal/tools/registry.go
  • internal/tools/tool.go
  • internal/workspace/coordination_commands.go
  • internal/workspace/coordination_commands_test.go
  • internal/workspace/discovery.go
  • internal/workspace/resolver_test.go
  • internal/workspaceaccess/audit.go
  • internal/workspaceaccess/default_policy.go
  • internal/workspaceaccess/default_policy_contracts.go
  • internal/workspaceaccess/default_policy_test.go
  • internal/workspaceaccess/default_policy_validation.go
  • internal/workspaceaccess/policy.go
  • magefiles/boundaries.go
  • web/src/routeTree.gen.ts
  • web/src/routes/_app/-agent-session-route-loader.ts
  • web/src/routes/_app/-session-permalink-route.ts
  • web/src/routes/_app/-session-workspace-switch.ts
  • web/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsx
  • web/src/routes/_app/__tests__/-route-preloading.integration.test.tsx
  • web/src/routes/_app/agents.$name.index.tsx
  • web/src/routes/_app/agents.$name.sessions.$id.tsx
  • web/src/routes/_app/agents.$name.tsx
  • web/src/routes/_app/agents.index.tsx
  • web/src/routes/_app/agents.tsx
  • web/src/routes/_app/session.$id.tsx
  • web/src/storybook/route-story-registry.ts
  • web/src/systems/os/components/index.ts
  • web/src/systems/os/components/os-route-hold.tsx
  • web/src/systems/os/index.ts
  • web/src/systems/os/lib/__tests__/routing-coordinator.test.ts
  • web/src/systems/os/lib/routing-coordinator.ts
  • web/src/systems/session/adapters/session-owner-api.ts
  • web/src/systems/session/components/session-workspace-switch-dialog.tsx
  • web/src/systems/session/index.ts
  • web/src/systems/session/lib/__tests__/session-deeplink-search.test.ts
  • web/src/systems/session/lib/session-deeplink-search.ts
  • web/src/systems/session/lib/session-owner-query.ts
  • web/src/systems/session/mocks/handlers.ts
  • web/src/systems/session/types.ts
💤 Files with no reviewable changes (2)
  • internal/api/core/agent_task_claim_criteria.go
  • internal/mcp/hosted_proxy.go

Comment thread internal/api/core/tool_errors.go Outdated
Comment on lines +119 to +121
if hasToolReason(toolErr.ReasonCodes, toolspkg.ReasonWorkspaceAccessDenied) {
payload.Message = strings.TrimSpace(toolErr.Message)
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not expose arbitrary denial messages.

Line 119 bypasses safeToolErrorMessage and returns toolErr.Message verbatim. Since workspace denials are 403, maskInternal does not redact it; any sensitive detail attached to a ToolError with this reason reaches the API response. Emit a canonical vetted denial message (or a dedicated vetted public-message field) instead.

  • internal/api/core/tool_errors.go#L119-L121: replace direct toolErr.Message forwarding with a stable safe workspace-access denial message.
  • internal/api/core/tools_test.go#L300-L316: assert the sanitized canonical message and verify a secret-bearing internal message is absent.

As per coding guidelines, “Never expose raw … secret bindings in logs, APIs, errors, messages, SSE, UI, or memory.”

📍 Affects 2 files
  • internal/api/core/tool_errors.go#L119-L121 (this comment)
  • internal/api/core/tools_test.go#L300-L316
🤖 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 `@internal/api/core/tool_errors.go` around lines 119 - 121, The
workspace-access-denied branch in internal/api/core/tool_errors.go (lines
119-121) must stop forwarding ToolError.Message and instead return a stable
vetted public denial message. Update the related assertions in
internal/api/core/tools_test.go (lines 300-316) to expect the canonical
sanitized message and verify that a secret-bearing internal message is not
present.

Source: Coding guidelines

Comment thread internal/api/udsapi/routes.go
Comment thread internal/cli/client_network_credentials.go
Comment thread internal/cli/workspace_test.go
Comment thread internal/daemon/daemon_mock_agents_integration_test.go
Comment thread internal/daemon/native_workspace_input_binder.go Outdated
Comment thread internal/session/spawn.go
Comment thread internal/task/manager_integration_test.go Outdated
Comment thread internal/tools/dispatch_test.go
Comment on lines +319 to 330
private startRouteReconciliation(
pending: RouteReconciliation,
outcome: WindowManagerOpenOutcome
): void {
if (!outcome.accepted) return;
this.routeReconciliation = { ...pending, inFlight: true };
void outcome.completion.then(() => {
if (this.routeReconciliation?.token !== pending.token) return;
this.routeReconciliation = null;
this.reportAuthoritativeState();
});
}

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

A rejected or failed open leaves routeReconciliation pending forever, muting authoritative URL sync.

Two failure paths never clear the pending entry:

  • if (!outcome.accepted) return; keeps routeReconciliation set with inFlight: false.
  • outcome.completion has no rejection handler; a rejected completion skips the then callback, so inFlight: true sticks.

Because reportAuthoritativeState returns early whenever routeReconciliation !== null (Line 124), the coordinator then stops truing the URL up to authoritative focus for the rest of the session — and in the rejected-outcome case it re-attempts openOrFocus on every authoritative frame.

🛡️ Proposed fix
   private startRouteReconciliation(
     pending: RouteReconciliation,
     outcome: WindowManagerOpenOutcome
   ): void {
-    if (!outcome.accepted) return;
+    if (!outcome.accepted) {
+      if (this.routeReconciliation?.token === pending.token) this.routeReconciliation = null;
+      return;
+    }
     this.routeReconciliation = { ...pending, inFlight: true };
-    void outcome.completion.then(() => {
-      if (this.routeReconciliation?.token !== pending.token) return;
-      this.routeReconciliation = null;
-      this.reportAuthoritativeState();
-    });
+    void outcome.completion
+      .catch(() => false)
+      .then(() => {
+        if (this.routeReconciliation?.token !== pending.token) return;
+        this.routeReconciliation = null;
+        this.reportAuthoritativeState();
+      });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private startRouteReconciliation(
pending: RouteReconciliation,
outcome: WindowManagerOpenOutcome
): void {
if (!outcome.accepted) return;
this.routeReconciliation = { ...pending, inFlight: true };
void outcome.completion.then(() => {
if (this.routeReconciliation?.token !== pending.token) return;
this.routeReconciliation = null;
this.reportAuthoritativeState();
});
}
private startRouteReconciliation(
pending: RouteReconciliation,
outcome: WindowManagerOpenOutcome
): void {
if (!outcome.accepted) {
if (this.routeReconciliation?.token === pending.token) this.routeReconciliation = null;
return;
}
this.routeReconciliation = { ...pending, inFlight: true };
void outcome.completion
.catch(() => false)
.then(() => {
if (this.routeReconciliation?.token !== pending.token) return;
this.routeReconciliation = null;
this.reportAuthoritativeState();
});
}
🤖 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 `@web/src/systems/os/lib/routing-coordinator.ts` around lines 319 - 330, Update
startRouteReconciliation to clear the matching routeReconciliation and call
reportAuthoritativeState when outcome.accepted is false, rather than returning
with pending state. Also handle rejected outcome.completion promises with the
same token-guarded cleanup and authoritative-state reporting used for successful
completion, while preserving protection against clearing a newer reconciliation.

@github-actions

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 8de642d.

@pedronauck
pedronauck merged commit 854fa2d into main Jul 29, 2026
10 of 12 checks passed
@pedronauck
pedronauck deleted the cross-workspaces branch July 29, 2026 21:07

This branch was successfully deployed

1 active deployment
Preview — 8de642d0 Deployed Jul 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant