Skip to content
This repository was archived by the owner on Aug 10, 2026. It is now read-only.

fix: persist active workspace and redirect on session/workspace mismatch - #238

Merged
pedronauck merged 3 commits into
compozy:mainfrom
igorcesarcode:fix/web-workspace-session-routing
Jun 2, 2026
Merged

pedronauck merged 3 commits into
compozy:mainfrom
igorcesarcode:fix/web-workspace-session-routing

Conversation

@igorcesarcode

@igorcesarcode igorcesarcode commented May 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Persists selectedWorkspaceId in localStorage via Zustand persist middleware so that direct navigation to a session URL (e.g. after a page refresh or sharing a link) resolves the correct workspace instead of falling back to the first one in the list.
  • Auto-sets workspace on first load: when selectedWorkspaceId is null, the first available workspace is automatically selected and persisted, avoiding the case where the fallback query.data[0] changes silently across refreshes.
  • Redirects immediately on workspace/session mismatch: a new useSessionWorkspaceGuard hook detects when the active workspace changes to one that does not own the current session and navigates to /agents/$name instantly — without waiting for TanStack Query's multi-second retry exhaustion on the 404.

Root cause

useActiveWorkspaceStore had no persistence — selectedWorkspaceId reset to null on every page load. useActiveWorkspace silently fell back to query.data[0], which could be a different workspace. When navigating directly to a session URL, the wrong workspace was used to fetch the session, so the transcript query was either fetching from the wrong workspace (empty result) or was disabled entirely.

Test plan

  • Navigate directly to a session URL after a page refresh — transcript should load correctly with the persisted workspace
  • Switch workspace while on a session that belongs to a different workspace — should redirect to /agents/$name immediately (< 500 ms), not after a 7+ second retry timeout
  • make web-lint passes (zero oxlint warnings)
  • bunx turbo run typecheck --filter=./web passes

Summary by CodeRabbit

  • New Features

    • Workspace selection is now persisted across browser sessions.
    • Sessions are resolved by ID and enforce workspace/agent consistency, with automatic redirection when mismatched.
  • Bug Fixes

    • Improved workspace selection logic to wait for persisted state, avoid invalid selections, and default to available workspaces.
    • Session loading now combines resolution and detail queries to prevent incorrect loading/error states.
  • Tests

    • Expanded test coverage for workspace persistence, session resolution, and redirection behavior.

Persists the selected workspace ID in localStorage so that direct
navigation to a session URL (e.g. after a page refresh) resolves
the correct workspace without falling back to the first available one.

- Add Zustand persist middleware to use-active-workspace-store with
  key "agh:active-workspace" and partialize on selectedWorkspaceId
- Auto-set selectedWorkspaceId to the first workspace when null so
  the selection is immediately persisted on first load
- Add useSessionWorkspaceGuard hook that detects workspace/session
  mismatch and navigates immediately to /agents/$name when the active
  workspace changes to one that does not own the current session,
  avoiding a multi-second wait for TanStack Query retry exhaustion
@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

@igorcesarcode is attempting to deploy a commit to the Compozy Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented May 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

Persists and hydrates the active workspace selection, gates active workspace exposure until hydration, adds workspace-resolution queries for sessions and a new useSessionById hook, and enforces session->workspace consistency via useSessionWorkspaceGuard integrated into session routes and covered by tests.

Changes

Workspace Validation and Persistence

Layer / File(s) Summary
Workspace persistence and hydration
web/src/systems/workspace/hooks/use-active-workspace-store.ts, web/src/systems/workspace/hooks/use-active-workspace.ts, web/src/systems/workspace/index.ts, web/src/test-setup.ts
Wraps the active-workspace Zustand store with persist (key "agh:active-workspace"), exposes useActiveWorkspaceStoreHasHydrated, gates activeWorkspace until hydration completes, and clears localStorage between tests.
Workspace hook tests
web/src/systems/workspace/hooks/__tests__/use-workspaces.test.tsx
Adds tests for hydrate-guarded selection and rehydration from localStorage; adds test helpers and a QueryClient wrapper used by the suite.
Session workspace resolution: keys/options/hooks
web/src/systems/session/lib/query-keys.ts, web/src/systems/session/lib/query-options.ts, web/src/systems/session/hooks/use-sessions.ts, web/src/systems/session/index.ts
Adds sessionKeys.resolveWorkspace, implements sessionWorkspaceResolutionOptions(id) that resolves a session's workspace_id from the sessions list, and introduces useSessionById(id) to resolve workspace before fetching session details; updates exports.
Session hooks tests
web/src/systems/session/hooks/__tests__/use-sessions.test.tsx
Expands session adapter mocks, adds error classes and a makeSession helper, and tests workspace-resolution behavior and "not found" handling for useSessionById.
Session workspace guard hook
web/src/hooks/routes/use-session-workspace-guard.ts
New useSessionWorkspaceGuard hook compares sessionWorkspaceId to activeWorkspaceId and navigates (replace) to /agents/$name when they differ.
Guard integration and route updates
web/src/routes/_app/agents.$name.sessions.$id.tsx, web/src/routes/_app/session.$id.tsx
Switches route session lookups to useSessionById, invokes useSessionWorkspaceGuard early in SessionPage, and keeps existing loading/not-found logic.
Route tests and test harness updates
web/src/routes/_app/__tests__/-agents.$name.sessions.$id.test.tsx, web/src/hooks/routes/__tests__/use-home-page.test.tsx
Wraps session page renders with a QueryClientProvider, adds workspace adapter and store mocking, extends session mocks (ledger/recap), and adds a test asserting redirect when a session's workspace differs from the active workspace.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • compozy/agh#80: Modifies the same /agents/$name/sessions/$id route and may overlap with session page behavior and routing changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: persist active workspace and redirect on session/workspace mismatch' directly captures the two main changes: persisting workspace selection and redirecting on workspace/session mismatches, matching the PR's core objectives.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 `@web/src/hooks/routes/use-session-workspace-guard.ts`:
- Around line 25-33: The guard effect only depends on activeWorkspaceId so it
misses the initial load when sessionWorkspaceRef.current (or sessionWorkspaceId)
is populated after mount; update the hook so the validation runs immediately by
adding sessionWorkspaceId (or sessionWorkspaceRef.current) to the dependency
array of the second useEffect (or merge the two useEffect blocks into one) and
perform the same mismatch check (compare sessionWorkspaceRef.current /
sessionWorkspaceId to activeWorkspaceId) and call navigate({ to:
"/agents/$name", params: { name: agentName }, replace: true }) when they differ.
🪄 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

Run ID: 878020f8-4908-4e8f-9447-4a60ef103b93

📥 Commits

Reviewing files that changed from the base of the PR and between cd4b155 and c7feafb.

📒 Files selected for processing (4)
  • web/src/hooks/routes/use-session-workspace-guard.ts
  • web/src/routes/_app/agents.$name.sessions.$id.tsx
  • web/src/systems/workspace/hooks/use-active-workspace-store.ts
  • web/src/systems/workspace/hooks/use-active-workspace.ts

Comment thread web/src/hooks/routes/use-session-workspace-guard.ts Outdated
Merge the two useEffect blocks into one so the mismatch check fires
immediately when sessionWorkspaceId arrives after mount, not only on
subsequent activeWorkspaceId changes.
@igorcesarcode

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@pedronauck pedronauck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated review — self + Codex (gpt-5.5, xhigh)

Independent automated review of PR #238 (web-only: persist active workspace + redirect on session/workspace mismatch). Authoritative change set is 4 files: use-session-workspace-guard.ts [new], agents.$name.sessions.$id.tsx, use-active-workspace-store.ts, use-active-workspace.ts.

  • Self verdict: REWORK (1 blocker, 2 risks, 1 nit)
  • Codex verdict: FIX_BEFORE_SHIP (2 blockers, 2 risks, 1 nit)
  • Combined: not shippable as-is.

This PR already carries a human CHANGES_REQUESTED, so it is not an auto-merge candidate regardless.

Blockers

  • [codex B-1 / self] The headline redirect fails on the exact reload / direct-nav path it targets. workspace_id does exist on the session response (optional), so the guard is wired — but useSession(id) fetches via the active workspace (GET /api/workspaces/{activeWorkspaceId}/sessions/{id}), and the backend is strictly workspace-scoped (requireSessionInWorkspace → 404 on mismatch). On initial load with a mismatched persisted workspace, the session never loads, session?.workspace_id is undefined, the guard ref stays empty, and the page falls through to the generic "Session not found" toast instead of redirecting. The redirect only works for the in-app workspace-switch case (truthful-UI gap). Fix: fetch the session by id independent of the active workspace (or resolve its workspace first), then redirect; add a test proving the reload/direct-nav mismatch redirects.
  • [codex B-2 / self] No tests, and the route now pulls real workspace TanStack Query state into a suite that can't provide it. The canonical -agents.$name.sessions.$id.test.tsx only wraps TopbarSlotProvider and mocks useSession — no QueryClient/workspace mock — so pulling useActiveWorkspace → useWorkspaces into the route likely breaks that suite and leaves both new invariants (persistence + redirect) uncovered. Fix: add the QueryClient/workspace test scaffolding and cover the persisted store and the redirect.

Risks

  • [self / codex R-1] Hydration race in use-active-workspace.ts:28-37. Auto-select-first-workspace can run before zustand persist rehydrates selectedWorkspaceId, causing flicker or an unintended redirect on first paint. Fix: gate auto-select on persist.hasHydrated() / onFinishHydration.
  • [codex R-2] Persisted localStorage store (agh:active-workspace) needs an explicit test reset — setup clears sessionStorage, not localStorage → cross-test state leakage. Fix: clear localStorage in test setup.

Nits

  • [codex N-1 / self] Render-phase ref mutation in use-session-workspace-guard.ts:19-26; the effect compares the mutable ref rather than the sessionWorkspaceId dependency. Fix: compute the derived value in the effect keyed on the id.

Codex ran once via compozy exec --ide codex --model gpt-5.5 --reasoning-effort xhigh (exit 0, findings validated). No source files modified.

@pedronauck
pedronauck merged commit 1dfd3ab into compozy:main Jun 2, 2026
2 of 3 checks passed
pedronauck added a commit that referenced this pull request Jun 2, 2026
…249)

## Summary

`main` (commit `8cc8ff39`) is red, blocking the **v0.0.6** release PR
#248. All three failing release-PR checks trace back to `main`, not the
release branch:

- **Verify** — `internal/daemon/coordinator_runtime.go` `bootstrapRun`
exceeded the gocyclo threshold (CC **21 > 20**). Extracted
`reconcileCreatedCoordinator` and `wakeCoordinatorIfNeeded` — a
behavior-preserving split that drops complexity to ~10.
- **Integration** —
`TestHarnessContextIntegrationStartupAndPromptShareResolverPolicy`
counted `<agh-situation-context>` (the **open** tag), which the bundled
tools guide (`tools-and-skills.md`) documents in prose, so the count was
2. It now counts the closing `</agh-situation-context>` tag → measures
**rendered sections** (exactly 1). The product already renders the
section once; the assertion was fragile (red since v0.0.5).
- **E2E Nightly** — the sandbox *"real session execution"* test
direct-navigates to a session created in a temp workspace while the
**global** workspace is active. #238's `useSessionWorkspaceGuard`
redirects away on a workspace/session mismatch, so the composer never
rendered and `locator.fill` hit the 180s timeout. The test now activates
the session's workspace via the switcher first — the
persisted-active-workspace precondition #238 assumes for direct
navigation.

## Verification

- `make verify` — **pass** (full monorepo gate).
- golangci-lint **v2.12.2** `./...` (CI's pinned version) — **0
issues**.
- `go test -race ./internal/daemon/...` — **875 unit pass**; **987
integration pass** (`-tags integration`).
- `bun-lint` — 0 warnings / 0 errors; web typecheck — pass.
- The E2E fix is verified by deterministic root-cause analysis +
typecheck/lint; the release PR's **E2E Nightly** lane (the lane that
failed) is its end-to-end gate. A faithful single-test local run is
blocked by the nightly coverage-gate plus the daemon-served harness env.

## Release impact

Merging this refreshes `release/v0.0.6` (#248) to include the fix so its
**Verify** and **Integration** lanes go green. Version stays **v0.0.6**
— CI's pinned git-cliff `2.7.0` treats `feat:` as a patch bump on `0.x`
(same as how v0.0.5 was cut).

> Note: v0.0.5's own release PR (#237) merged with **Integration +
Verify failing**. The heavy lanes (Integration, E2E Nightly) only run on
release PRs, so these regressions slipped past per-feature CI. Worth
tightening the merge gate separately.

AGH Impact Audit:

- Native tools: no impact — no `agh__*` IDs, toolsets, descriptors,
input/output schemas, schema digests, risk flags, or capability gates
changed; this is an internal daemon refactor plus two test files.
- Extensibility and hooks: no impact — no extension manifests/resources,
hook taxonomy or dispatch call sites, skill/capability declarations,
bundles, registries, bridge SDKs, MCP sidecars, or `config.toml`
lifecycle changed. `bootstrapRun`'s dispatch call sites, locking, and
ordering are preserved by the extraction.
- Workspace data isolation: no impact on runtime data ownership — the
coordinator refactor preserves `workspace_id`-scoped
bootstrap/reconcile/dispatch exactly; the e2e change only makes the test
honor the existing per-workspace session-route guard from #238.
- Official AGH skill: no impact — `skills/agh/SKILL.md` and
`skills/agh/references/*.md` are unchanged. The `tools-and-skills.md`
`<agh-situation-context>` mention is correct documentation and
intentionally left as-is; the test was corrected instead.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Improved internal coordinator session initialization and management
code organization for better maintainability and clarity.

* **Tests**
* Updated integration tests with refined assertions to better validate
rendered output correctness.
* Enhanced end-to-end tests with explicit verification steps for
workspace-to-session binding workflows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
This was referenced Jun 2, 2026

This branch was successfully deployed

1 active deployment
Preview — ddbb788f Deployed Jun 2, 2026 by vercel[bot]
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants