Skip to content

feat: scope GitHub integration settings by workspace#1572

Merged
carlosflorencio merged 16 commits into
mainfrom
feature/integrations-per-wor-1rk
Jul 6, 2026
Merged

feat: scope GitHub integration settings by workspace#1572
carlosflorencio merged 16 commits into
mainfrom
feature/integrations-per-wor-1rk

Conversation

@carlosflorencio

@carlosflorencio carlosflorencio commented Jul 1, 2026

Copy link
Copy Markdown
Member

Workspace-specific integration setup was global enough that GitHub defaults, scopes, and watch behavior could bleed across workspaces. This adds a workspace-aware settings layer so each workspace can choose all GitHub repos, selected orgs, or selected repos while existing global settings migrate forward through the first workspace path.

Important Changes

  • Added backend workspace GitHub settings storage, migration, API endpoints, and scoped repo enforcement for PR/issue search and watch polling.
  • Added a top workspace switcher for integration settings and moved GitHub repo scope, watches, presets, and default queries onto the selected workspace.
  • Added an ADR documenting workspace-scoped integration settings and migration behavior.

Validation

  • make fmt
  • make typecheck
  • make test
  • make lint
  • go test ./internal/github
  • pnpm e2e:run --host tests/integrations/github-workspace-settings.spec.ts -- --project=chromium

Possible Improvements

Medium risk: Linear and other integrations still need to be moved onto the same workspace-scoped settings pattern.

Closes #1481

Checklist

  • I have performed a self-review of my code.
  • I have manually tested my changes and they work as expected.
  • My changes have tests that cover the new functionality and edge cases.
  • If my change touches UI files (apps/web/), I have added or updated Playwright e2e tests in apps/web/e2e/ and verified them with make test-e2e.

Review in cubic

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR scopes GitHub, Jira, Linear, Sentry, and Slack integration data, handlers, clients, and UI by workspace. It adds workspace-owned settings storage, migration paths, workspace-aware search/watch/polling behavior, and updated frontend/API wiring across all five integrations.

Changes

Shared workspace infrastructure

Layer / File(s) Summary
Workspace resolver
apps/backend/internal/integrations/workspacescope/*
Adds cached workspace migration-target resolution and tests.
ADR
docs/decisions/0030-...md, docs/decisions/INDEX.md
Adds and indexes the workspace-scoped integration settings decision.

GitHub

Layer / File(s) Summary
Settings storage
apps/backend/internal/github/models.go, store.go
Adds workspace settings types and github_workspace_settings storage with get/upsert/patch behavior.
Search, watch, and cleanup
workspace_settings_service.go, service_cleanup.go, service_issues.go, service_reviews.go, tests
Adds workspace-scoped settings CRUD, search paging, watch checks, and cleanup paths.
Controllers and WS trigger
controller.go, handlers.go, backendapp/e2e_reset.go
Adds workspace-settings routes and workspace_id handling for search, cleanup, trigger, and watch mutations.
Frontend API and types
apps/web/lib/api/domains/github-api.ts, lib/types/github.ts, e2e/helpers/api-client.ts
Scopes GitHub watch, cleanup, search, and workspace-settings requests by workspace_id.
Presets and repo scope
use-default-query-presets.ts, use-saved-presets.ts, repo-filter-selector.tsx, github-page-client.tsx, tests
Moves saved/default query presets to per-workspace state and adds workspace repo-scope filtering to GitHub search and selection UI.
Settings page and e2e
github-settings.tsx, workspace-scoped-section.tsx, e2e specs
Scopes watch actions and adds workspace-scoped layout controls and e2e coverage.
Backend reset
apps/backend/internal/backendapp/e2e_reset.go
Clears workspace-scoped GitHub workspace settings during E2E reset.

Jira

Layer / File(s) Summary
Model and legacy secret migration
models.go, provider.go
Adds workspace IDs to Jira config models and updates legacy secret migration direction.
Store migration
store.go, store_test.go
Rekeys Jira config storage by workspace_id and adds per-workspace CRUD, health, and migration handling.
Service and poller wiring
service.go, service_issue_watch.go, poller_*, tests
Adds per-workspace Jira client caching and workspace-aware config, auth, ticket, and poller helper methods.
HTTP handlers
handlers.go, mock_controller.go
Routes Jira HTTP endpoints through workspace-scoped service and client methods.
Frontend API and UI
jira-api.ts, use-jira-availability.ts, jira-settings.tsx, jira-page-client.tsx, tests
Threads workspaceId through Jira API clients, availability hooks, settings, ticket, and page code.

Linear

Layer / File(s) Summary
Model and legacy secret migration
models.go, provider.go
Adds workspace IDs to Linear config models and updates legacy secret migration direction.
Store migration
store.go, store_test.go
Rekeys Linear config storage by workspace_id and adds per-workspace CRUD, health, and migration handling.
Service and poller wiring
service.go, service_issue_watch.go, poller_*, tests
Adds per-workspace Linear client caching and workspace-aware config, auth, issue, and poller helper methods.
HTTP handlers
handlers.go, mock_controller.go
Routes Linear HTTP endpoints through workspace-scoped service and client methods.
Frontend API and UI
linear-api.ts, use-linear-availability.ts, linear-settings.tsx, linear-page-client.tsx, tests
Threads workspaceId through Linear API clients, availability hooks, settings, issue, and page code.

Sentry

Layer / File(s) Summary
Model and legacy secret migration
models.go, provider.go
Adds workspace IDs to Sentry config models and wires a legacy secret migration step into provider initialization.
Store migration
store.go, store_test.go
Rekeys Sentry config storage by workspace_id and adds per-workspace CRUD and migration handling.
Service and poller wiring
service.go, service_issue_watch.go, poller_*, tests
Adds per-workspace Sentry client caching and workspace-aware config, auth, issue, and poller helper methods.
HTTP handlers
handlers.go, mock_controller.go
Routes Sentry HTTP endpoints through workspace-scoped service and client methods.
Frontend API and UI
sentry-api.ts, use-sentry-availability.ts, sentry-settings.tsx, sentry-issue-*, sentry-link-button.tsx
Threads workspaceId through Sentry API clients, availability hooks, issue/watch/link settings, and page wiring.

Slack

Layer / File(s) Summary
Model and legacy secret migration
models.go, provider.go
Adds workspace IDs to Slack config models and reverses legacy secret migration direction.
Store migration
store.go, store_test.go
Rekeys Slack config storage by workspace_id and adds per-workspace CRUD, listing, and migration handling.
Service and trigger wiring
service.go, trigger.go, trigger_test.go, tests
Adds per-workspace Slack client caching, workspace-aware auth/config methods, and per-workspace polling cadence.
HTTP handlers
handlers.go, mock_controller.go
Routes Slack HTTP endpoints through workspace-scoped service methods.
Frontend API and UI
slack-api.ts, use-slack-availability.ts, slack-settings.tsx, settings-layout-client.tsx
Threads workspaceId through Slack API clients, availability hooks, settings components, and the settings top bar.

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

Sequence Diagram(s)

sequenceDiagram
  participant Controller
  participant Service
  participant Store
  participant GitHubClient
  Controller->>Service: SearchUserPRsPagedForWorkspace(workspaceID, query)
  Service->>Store: GetWorkspaceSettings(workspaceID)
  Store-->>Service: WorkspaceSettings
  Service->>Service: append scope qualifiers to query
  Service->>GitHubClient: search PRs with scoped query
  GitHubClient-->>Service: PR results
  Service->>Service: filterPRsByWorkspaceScope
  Service-->>Controller: PRSearchPage
Loading

Possibly related PRs

  • kdlbs/kandev#672: Same GitHub issue-watch area; this PR scopes the watch and cleanup flow introduced there.
  • kdlbs/kandev#1440: Same GitHub preset/default-query syncing flow; this PR extends that logic to be workspace-scoped.
  • kdlbs/kandev#771: Same E2E integration plumbing used for workspace-scoped config seeding and cleanup.

Poem

I hopped through workspaces, light as a breeze,
Each repo now stays in its own little trees.
GitHub, Jira, Linear, Slack, and Sentry too,
All scoped to the burrow that fits just right for you.
No mix-ups now — just tidy, snug queues. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also includes large Jira, Linear, Sentry, and Slack workspace-scoping changes that are unrelated to the GitHub-only linked issue. Split the non-GitHub integration changes into separate PRs, or remove them, so this PR only covers GitHub workspace repository binding.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements workspace-scoped GitHub repo binding, filtering, persistence, and tests that align with the linked issue's core requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely matches the main change: scoping GitHub integration settings by workspace.
Description check ✅ Passed The description covers the required summary, key changes, validation, checklist, and related issue, with only optional sections omitted.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/integrations-per-wor-1rk

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.

@claude

claude Bot commented Jul 1, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 5m 37s —— View job


Code Review

Todo

  • Load review skill and gather context
  • Review backend changes (Go)
  • Review frontend changes (TypeScript/React)
  • Review tests
  • Review ADR and documentation
  • Post findings

Findings

Blocker (must fix before merge)

  1. httpUpdateWorkspaceSettings maps all service errors to 400controller.go:885

    • Issue: DB errors from GetWorkspaceSettings and UpsertWorkspaceSettings are returned as 400 Bad Request instead of 500 Internal Server Error.
    • Why: 400 signals a client mistake; a database failure is a server error. This makes incidents harder to diagnose and will cause the frontend to silently swallow what looks like a "bad request".
    • Fix: Return 400 only for validation errors (e.g., workspace_id is required), 500 for everything else. See inline comment for a suggested approach. Fix this →
  2. Missing unit test for UpdateWorkspaceSettings partial-update logicworkspace_settings_service.go:28-58

    • Issue: The DefaultQueryPresets null-literal clearing path (string(*req.DefaultQueryPresets) == jsonNullLiteral → current.DefaultQueryPresets = nil) and the general partial-update semantics (nil fields unchanged) have no direct test.
    • Why: Per the repo's testing conventions, new service methods with non-trivial logic require a *_test.go test. This code path could regress silently.
    • Fix: Add TestService_UpdateWorkspaceSettings_PartialUpdate to workspace_settings_test.go. See inline comment for a ready-to-use test.

Suggestion (recommended, doesn't block)

  1. Scoped search TotalCount reflects per-page count, not totalworkspace_settings_service.go:163-177

    • After client-side scope filtering, result.TotalCount = len(result.PRs) is set to the filtered count on the current page only. Page 1 might return TotalCount=20, page 2 might return TotalCount=8. If the frontend uses this field for "N total" display or page-count calculation, the values will be inconsistent.
  2. Unknown RepoScopeMode silently coerced to "all"store.go:1979

    • normalizeRepoScopeMode maps any unrecognized mode to "all" without an error, so a typo in a request produces a 200 with a silently-different value. Consider returning a validation error in UpdateWorkspaceSettings when an unrecognized mode is supplied (matching how CleanupPolicy is validated in CreateReviewWatch).
  3. Legacy sync hooks run unnecessarily in workspace modeuse-saved-presets.ts:207, use-default-query-presets.ts:229

    • useLegacySavedPresetsSync / useLegacyDefaultQueryPresetSync always fire on mount, even when workspaceId is set. These trigger an unneeded fetchUserSettings call per mount in workspace mode. Not a correctness issue — a minor perf nit.

Summary

Severity Count
Blocker 2
Suggestion 3

Verdict: Blocked — fix blockers first.

The core feature — per-workspace repo scope, migrated presets, and watch filtering — is well-structured. The store roundtrip tests and the scoped watch tests are solid. The two blockers are both small fixes: correct the HTTP status code and add one test method.

Comment thread apps/backend/internal/github/controller.go Outdated
Comment thread apps/backend/internal/github/workspace_settings_service.go
@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a workspace-aware settings layer to GitHub integrations, allowing each workspace to independently configure its repo scope (all, by org, or by specific repos), saved presets, and default query presets. Existing global settings are migrated forward through a one-time migration on first workspace load.

  • Backend: New github_workspace_settings SQLite table with GET/PUT /workspace-settings endpoints; scoped PR/issue search and watch polling; IDOR guards (requireReviewWatchInWorkspace, requireIssueWatchInWorkspace) on mutation endpoints; and per-workspace cleanup sweep variants.
  • Frontend: New WorkspaceScopedSection wrapper, per-workspace preset hooks with a two-stage migration path (localStorage → legacy backend → workspace settings), and updated RepoFilterSelector with case-insensitive scope enforcement.
  • ADR 0030: Documents the decision and migration strategy.

Confidence Score: 4/5

Safe to merge with the known workspace sync and pagination trade-offs; no data corruption paths were found

The backend scope enforcement is correct and well-tested — IDOR guards close cross-workspace watch mutations, case-insensitive matching is consistent between frontend and backend, and the DB schema and migration path are sound. The main concerns are in the workspace preset sync hooks: network failures during a save don't set a retry flag (unlike the legacy global sync), leaving users with lost preset changes after a reload; and the module-level sync queue serializes writes across all workspaces unnecessarily.

apps/web/components/github/my-github/use-saved-presets.ts and use-default-query-presets.ts — workspace sync failure handling and shared queue design differ from the battle-tested global sync pattern

Important Files Changed

Filename Overview
apps/backend/internal/github/workspace_settings_service.go Core workspace-scoped search and filter logic; introduces scoped PR/issue search with cache, scope qualifiers, and post-filter. Case-insensitive comparison in repoAllowedByWorkspaceScope is correct.
apps/backend/internal/github/store.go Adds github_workspace_settings table and all CRUD operations. Patch uses INSERT OR IGNORE + UPDATE; reads normalize via normalizeWorkspaceSettings.
apps/backend/internal/github/workspace_settings_test.go Good test coverage: round-trip, scope appending, org scope, empty-scope fast-path, partial update, explicit null clear, and watch scope enforcement for both PR and issue pollers.
apps/backend/internal/github/controller.go Adds workspace_settings GET/PUT endpoints and workspace_id-gated variants of PR/issue search and cleanup; IDOR guards added to update/delete watch paths.
apps/web/components/github/my-github/use-saved-presets.ts Adds per-workspace preset storage with two-stage migration. Save/remove are guarded against writing while loading. Workspace sync failures are silently dropped without a retry flag.
apps/web/components/github/my-github/use-default-query-presets.ts Mirrors use-saved-presets for default query presets with the same migration path and the same silent failure concern on network error.
apps/web/components/github/repo-filter-selector.tsx repoAllowedByScope and orgAllowedByScope both use .toLowerCase() on both sides. Workspace scope filters the org badges and repo combobox correctly.
apps/backend/internal/github/service_cleanup.go Adds CleanupReviewTasksForWorkspace and CleanupIssueTasksForWorkspace; fetches all tasks then filters in-memory by workspace watch IDs.
apps/backend/internal/github/models.go Adds WorkspaceSettings and UpdateWorkspaceSettingsRequest types with custom UnmarshalJSON tracking explicit null vs absent fields.
apps/web/components/integrations/workspace-scoped-section.tsx Clean workspace switcher wrapper; override survives until workspace list no longer contains it.
apps/web/lib/api/domains/github-api.ts Adds fetchGitHubWorkspaceSettings and updateGitHubWorkspaceSettings; search functions pass workspace_id when provided.
apps/backend/internal/backendapp/e2e_reset.go Adds cleanup of github_workspace_settings for the test workspace on e2e reset.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant FE as Frontend Hook
    participant API as Backend API
    participant DB as SQLite

    note over FE,DB: First load for a workspace
    FE->>API: "GET /workspace-settings?workspace_id=ws1"
    API->>DB: "SELECT github_workspace_settings WHERE workspace_id=ws1"
    DB-->>API: ErrNoRows (first time)
    API-->>FE: "default settings (mode=all, empty orgs/repos)"

    note over FE: Migration: push legacy localStorage/user-settings to workspace
    FE->>API: "PUT /workspace-settings {workspace_id, saved_presets}"
    API->>DB: INSERT OR IGNORE + UPDATE github_workspace_settings
    DB-->>API: ok
    API-->>FE: WorkspaceSettings
    FE->>FE: markMigratedToWorkspace(ws1)

    note over FE,DB: Scoped PR search
    FE->>API: "GET /user/prs?workspace_id=ws1&query=is:open"
    API->>DB: GetWorkspaceSettings(ws1)
    DB-->>API: "{mode:repos, repos:[kdlbs/kandev]}"
    API->>API: appendWorkspaceScopeToSearch
    API->>API: GitHub Search API + filterPRsByWorkspaceScope
    API-->>FE: "{prs:[], total_count: N}"

    note over FE,DB: IDOR-guarded watch mutation
    FE->>API: "PUT /watches/review/:id?workspace_id=ws1"
    API->>DB: GetReviewWatch(id)
    DB-->>API: "{watch.WorkspaceID=ws1}"
    API->>API: requireReviewWatchInWorkspace match check
    API->>DB: UpdateReviewWatch
    API-->>FE: ReviewWatch
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant FE as Frontend Hook
    participant API as Backend API
    participant DB as SQLite

    note over FE,DB: First load for a workspace
    FE->>API: "GET /workspace-settings?workspace_id=ws1"
    API->>DB: "SELECT github_workspace_settings WHERE workspace_id=ws1"
    DB-->>API: ErrNoRows (first time)
    API-->>FE: "default settings (mode=all, empty orgs/repos)"

    note over FE: Migration: push legacy localStorage/user-settings to workspace
    FE->>API: "PUT /workspace-settings {workspace_id, saved_presets}"
    API->>DB: INSERT OR IGNORE + UPDATE github_workspace_settings
    DB-->>API: ok
    API-->>FE: WorkspaceSettings
    FE->>FE: markMigratedToWorkspace(ws1)

    note over FE,DB: Scoped PR search
    FE->>API: "GET /user/prs?workspace_id=ws1&query=is:open"
    API->>DB: GetWorkspaceSettings(ws1)
    DB-->>API: "{mode:repos, repos:[kdlbs/kandev]}"
    API->>API: appendWorkspaceScopeToSearch
    API->>API: GitHub Search API + filterPRsByWorkspaceScope
    API-->>FE: "{prs:[], total_count: N}"

    note over FE,DB: IDOR-guarded watch mutation
    FE->>API: "PUT /watches/review/:id?workspace_id=ws1"
    API->>DB: GetReviewWatch(id)
    DB-->>API: "{watch.WorkspaceID=ws1}"
    API->>API: requireReviewWatchInWorkspace match check
    API->>DB: UpdateReviewWatch
    API-->>FE: ReviewWatch
Loading

Reviews (5): Last reviewed commit: "fix: make workspace switcher look select..." | Re-trigger Greptile

Comment thread apps/backend/internal/github/workspace_settings_test.go
Comment thread apps/backend/internal/github/workspace_settings_service.go
Comment thread apps/web/components/github/repo-filter-selector.tsx
Comment thread apps/backend/internal/github/store.go
Comment thread apps/web/components/github/my-github/use-saved-presets.ts Outdated
Comment thread apps/web/components/github/repo-filter-selector.tsx
Comment thread apps/web/components/github/my-github/use-default-query-presets.ts Outdated
Comment thread apps/backend/internal/github/controller.go

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

<div className="flex items-center gap-2">
<CleanupNowButton label="Clean up merged" run={cleanupMergedReviewTasks} />

P2 Badge Scope cleanup actions to the selected workspace

These watch sections now live under a single selected workspace and hide the workspace column, but cleanupMergedReviewTasks and the matching issue cleanup still call install-wide backend sweeps. From workspace A's GitHub settings, clicking Clean up merged/closed can delete auto-created tasks owned by watches in workspace B. Pass the selected workspace_id to a scoped cleanup endpoint, or move these buttons to a clearly global area.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/backend/internal/github/workspace_settings_service.go
Comment thread apps/backend/internal/github/service_reviews.go
Comment thread apps/web/components/github/github-settings.tsx
Comment thread apps/backend/internal/github/workspace_settings_service.go Outdated
Comment thread apps/web/components/github/my-github/use-default-query-presets.ts Outdated
Comment thread apps/web/components/github/my-github/use-saved-presets.ts Outdated
Comment thread apps/backend/internal/github/workspace_settings_service.go Outdated
Comment thread apps/web/app/github/github-page-client.tsx Outdated
Comment thread apps/web/components/github/my-github/use-saved-presets.ts

@cubic-dev-ai cubic-dev-ai 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.

24 issues found across 25 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/backend/internal/github/workspace_settings_service.go Outdated
Comment thread apps/backend/internal/github/workspace_settings_service.go Outdated
Comment thread apps/backend/internal/github/store.go
Comment thread apps/backend/internal/github/service_reviews.go
Comment thread apps/backend/internal/github/workspace_settings_service.go
Comment thread apps/web/app/github/github-page-client.tsx Outdated
Comment thread apps/web/components/github/my-github/use-saved-presets.ts Outdated
Comment thread apps/backend/internal/github/workspace_settings_service.go Outdated
Comment thread apps/web/components/github/my-github/use-saved-presets.ts Outdated
Comment thread apps/backend/internal/github/workspace_settings_service.go
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 31s —— View job


Code Review

Todo

  • Load review skill and gather context
  • Review the diff and existing review findings
  • Check what issues from prior reviews have been addressed vs. still outstanding
  • Review backend changes (Go)
  • Review frontend changes (TypeScript/React)
  • Post findings as inline comments + summary

Comment thread apps/web/components/settings/settings-layout-client.tsx Outdated
@carlosflorencio carlosflorencio force-pushed the feature/integrations-per-wor-1rk branch from 87c5a84 to a91f594 Compare July 2, 2026 00:16
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 0s —— View job


I'll analyze this and get back to you.

Comment thread apps/web/components/github/my-github/use-saved-presets.ts

@cubic-dev-ai cubic-dev-ai 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.

6 issues found across 15 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread apps/backend/internal/github/workspace_settings_service.go Outdated
Comment thread apps/web/components/github/my-github/use-saved-presets.ts
Comment thread apps/web/components/github/my-github/use-github-search.ts Outdated
Comment thread apps/web/components/github/my-github/use-saved-presets.ts Outdated
Comment thread apps/web/components/github/my-github/use-default-query-presets.ts Outdated
Comment thread apps/web/components/github/my-github/use-default-query-presets.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a91f594bdc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/backend/internal/github/workspace_settings_service.go Outdated
Comment thread apps/web/components/github/github-settings.tsx
Comment thread apps/backend/internal/github/store.go
Comment thread apps/web/components/github/my-github/use-saved-presets.ts Outdated
Comment thread apps/backend/internal/github/workspace_settings_service.go Outdated
Comment thread apps/backend/internal/github/workspace_settings_test.go
Comment thread apps/web/components/github/my-github/use-default-query-presets.ts
Comment thread apps/backend/internal/github/workspace_settings_service.go Outdated
Comment thread apps/web/app/github/github-page-client.tsx Outdated
Comment thread apps/backend/internal/github/store.go
@carlosflorencio carlosflorencio force-pushed the feature/integrations-per-wor-1rk branch from a91f594 to d774475 Compare July 2, 2026 11:14
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 5m 43s —— View job


Code Review

Todo

  • Load review skill and gather context
  • Read changed files for this review round
  • Review remaining open issues
  • Post inline findings
  • Post summary

Findings

All P1 blockers raised in prior review rounds are confirmed fixed in this round. The issues below are the only remaining concerns.

Suggestion (recommended, doesn't block)

  1. Cleanup sweep buttons are workspace-unawareapps/web/components/github/github-settings.tsx:289

    • Issue: cleanupMergedReviewTasks and cleanupClosedIssueTasks hit unscoped backend endpoints. Moving ReviewWatchSection and IssueWatchSection into PerWorkspaceSection makes these buttons visually workspace-scoped, but a click still sweeps tasks across all workspaces.
    • Fix: Pass workspaceId to scoped cleanup endpoints, or move the buttons outside PerWorkspaceSection to a clearly global area. See inline comment.
  2. Missing remove-while-loading testapps/web/components/github/my-github/use-saved-presets.test.ts:229

    • Issue: The save loading guard has a test; the identical remove guard does not.
    • Fix: Add a parallel test case. Template in the inline comment.

What this round verified as fixed

  • ✅ OR qualifier grouping for multi-repo/org workspace scopes ((repo:a/b OR repo:c/d))
  • ✅ HTTP status codes: validation errors → 400, store failures → 500
  • save / remove guard when workspacePresets === undefined during loading window
  • useLayoutEffect for workspaceRef synchronization in useGitHubSearch
  • PatchWorkspaceSettings avoids read-modify-write race via column-specific SQL patch
  • ✅ Legacy sync hooks disabled when workspace mode is active (enabled parameter)
  • setProgrammaticSelection / setUserSelection naming disambiguates use cases
  • ✅ Case-insensitive frontend scope filtering
  • ✅ Unknown repo_scope_mode rejected with a validation error
  • ✅ E2E reset includes github_workspace_settings cleanup
  • ✅ Review/issue watch hooks refetch when the selected workspace changes
  • ✅ Integration workspace persistence serialized via createQueuedUserSettingsSync
  • saved_presets null unmarshal tracking test added
  • ✅ Workspace migration marker set only after a successful write

Summary

Severity Count
Blocker 0
Suggestion 2

Verdict: Ready to merge — the two suggestions are follow-up scope (one is a pre-existing behavior that's now more noticeable, one is a minor test gap). No blockers remain.

Comment thread apps/web/components/github/github-settings.tsx
Comment thread apps/web/components/github/my-github/use-saved-presets.test.ts
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

Code Review

Todo

  • Load tools and skill
  • Read recent commits since last review
  • Review new changes (Jira/Sentry/Slack migration tests, latest fixups)
  • Post inline findings
  • Post summary

View job run

@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 3m 3s —— View job


Code Review

Todo

  • Load tools and skill
  • Read skill file and latest commits
  • Review new changes (migration test coverage, workspace fallback caching)
  • Post inline findings and summary

What this round verified as fixed

The last review round raised two required gaps and one suggestion. All three are addressed in commits 7200a45 and 1f3fb03.

  • Jira singleton migration testTestStore_MigrateSingletonConfigToActiveWorkspace seeds a workspaces table + user settings, sets active workspace to ws-active, seeds a singleton config, calls NewStore, and asserts the config migrated to ws-active with correct field values.
  • Sentry singleton migration test — Same pattern, asserts URL field is preserved across the migration.
  • Slack singleton migration test — Same pattern, asserts UtilityAgentID, CommandPrefix, PollIntervalSeconds, SlackTeamID, SlackUserID, and LastSeenTS are all preserved.
  • defaultWorkspaceID() re-running on every call — Addressed via workspacescope.CachedResolver, which memoizes the first successful resolution with a mutex-guarded string field. Wired into all four stores (Jira, Linear, Sentry, Slack).

The lifecycle complexity fix (5b40877) is a clean extraction of restoreRecoveredFailedExecution with no logic changes. The ADR docs update (8bfab78) correctly marks the cross-workspace copy action as a future follow-up rather than claiming it is included.


Findings

Suggestion (recommended, doesn't block)

  1. CachedResolver has no unit testapps/backend/internal/integrations/workspacescope/workspacescope.go:24

    • Issue: CachedResolver.Resolve is new logic with no *_test.go file in the package. The store integration tests exercise ResolveMigrationTarget indirectly, but the caching layer (second-call returns cached value, error from ResolveMigrationTarget is not cached) is untested.
    • The "don't retry on error" behavior is important: if the first Resolve call fails, r.workspaceID stays "" and the next call retries. A test for this path would pin the contract.
  2. FallbackWorkspaceID ("default") gets permanently cached if resolved before workspaces existworkspacescope.go:27

    • When ResolveMigrationTarget returns FallbackWorkspaceID = "default" (no workspaces table or empty table), that non-empty string is stored in r.workspaceID. Subsequent workspace creation is never picked up by the legacy paths.
    • Before this PR, ResolveMigrationTarget re-ran on every call, so a newly-created workspace would be picked up on the next WS handler invocation. With the cache, that's no longer the case.
    • In practice the impact is minimal — legacy WS handlers shouldn't be called before any workspaces exist, and new paths explicitly pass workspace_id. But the comment "used only in isolated tests or malformed databases" on FallbackWorkspaceID now also describes a transient early-startup state that could arise naturally. Worth noting if an edge case report surfaces.

Summary

Severity Count
Blocker 0
Suggestion 2

Verdict: Ready to merge. All blockers from prior rounds are resolved. The two suggestions are low-probability edge cases in simple new caching code and can be followed up separately.

@cubic-dev-ai cubic-dev-ai 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.

5 issues found across 8 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread apps/backend/internal/sentry/store_test.go
Comment thread apps/backend/internal/integrations/workspacescope/workspacescope.go
Comment thread apps/backend/internal/jira/store_test.go
Comment thread apps/backend/internal/slack/store_test.go
Comment thread apps/backend/internal/integrations/workspacescope/workspacescope.go
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 3m 33s —— View job


Code Review

Todo

  • Load tools
  • Read SKILL.md and check new commits since last review
  • Review new changes (31b475e — harden integration workspace migration coverage)
  • Post inline findings and summary

What this round verified as fixed

Commit 31b475e directly addresses both suggestions from the previous review round.

  • CachedResolver unit tests addedworkspacescope_test.go (new, 135 lines) covers three behaviors: cache-hit reuse (TestCachedResolverReusesSuccessfulResolution), per-DB identity re-resolution (TestCachedResolverCachesPerDB), and error non-memoization (TestCachedResolverDoesNotMemoizeErrors). All three tests are correct and well-structured.
  • CachedResolver per-DB identity addedworkspacescope.go now stores the *sqlx.DB pointer alongside the cached workspace ID and only returns the cached value when both the pointer and the ID match. This prevents a shared resolver (unusual in production but possible in tests) from returning a stale value when called with a different DB instance.
  • Migration field coverage hardened — Jira, Sentry, and Slack TestStore_MigrateSingletonConfigToActiveWorkspace tests now seed last_checked_at, last_ok, and last_error in the singleton INSERT, and assert all three fields survive migration intact. Previously only URL/key/team fields were checked.

Findings

No blockers. No suggestions. The only remaining note from the previous round (that FallbackWorkspaceID = "default" could be permanently cached before workspaces are created on the same DB instance) is correctly scoped by the inline comment — production installs always have at least one workspace at store-init time — and the per-DB check is a net improvement for test isolation. Nothing further to act on.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge. All blockers from all prior review rounds are resolved, and the latest commit adds the coverage that was still outstanding.

@carlosflorencio carlosflorencio force-pushed the feature/integrations-per-wor-1rk branch from 31b475e to 3699460 Compare July 3, 2026 22:04
@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 3m 15s —— View job


Code Review

Todo

  • Load tools
  • Read SKILL.md and check new commits since last review
  • Review new changes (5 commits: CachedResolver, migration tests, Jira refactor)
  • Post inline findings and summary

Reviewed commits since last round: 3699460, e883cfb, d1666d6, 8d82e58, 4807292 (CachedResolver, singleton migration tests for Jira/Sentry/Slack, Jira workspace refactor, ADR clarification).

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge. All blockers from all prior review rounds are resolved.

@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown

Code Review

Reviewed commits since last round: 3699460 (resolve rebase conflicts), e883cfb (harden migration coverage), d1666d6 (cache legacy workspace fallback), 8d82e58 (cover active workspace migrations), 4807292 (docs clarification).


Findings

No blockers. No suggestions.

What this round verified

  • CachedResolver implementation — Correct mutex-guarded caching keyed on both *sqlx.DB pointer and workspace ID. Errors are not memoized; a failed Resolve call retries on next invocation. Thread-safe.
  • CachedResolver test suite — Three focused tests cover: cache-hit reuse across DB state changes (TestCachedResolverReusesSuccessfulResolution), per-DB identity re-resolution (TestCachedResolverCachesPerDB), and error non-memoization (TestCachedResolverDoesNotMemoizeErrors). All three cover the material contracts.
  • Per-store CachedResolver wiring — Jira, Linear, Sentry, and Slack stores add a defaultWorkspace workspacescope.CachedResolver value field and route defaultWorkspaceID() through it. Go correctly takes &s.defaultWorkspace for the pointer-receiver method because the store struct is always accessed via pointer — no implicit copy issue.
  • Jira ListProjectStatusesForWorkspace refactor — Handler now passes c.workspaceID(ctx) through the new workspace-scoped variant; legacy callers route through defaultWorkspaceID(). Consistent with the pattern used throughout the rest of this PR.
  • Singleton migration tests (Jira, Sentry, Slack)TestStore_MigrateSingletonConfigToActiveWorkspace added to all three, matching the Linear test from the previous round. All three assert field-level values including last_checked_at, last_ok, and last_error, pinning the fields-survive-migration invariant.
  • ADR clarity — Copy-action language updated to mark it as a future follow-up rather than an included feature.
  • Test assertion fixtoHaveAttributegetAttribute().toBe() in workspace-picker test; aligns with how the project's test utilities resolve DOM attributes.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge. All blockers from all prior review rounds are resolved. This round closes the remaining gaps: CachedResolver caching is correct and tested, and Jira/Sentry/Slack singleton migration tests match the Linear model.

@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 56s —— View job


Code Review

Todo

  • Load tools
  • Read SKILL.md and check new commits since last review
  • Review latest changes
  • Post inline findings and summary

What this round verified

Only one new commit since the previous review round (bf51332): a pure formatting fix that collapses the listJiraProjectStatuses function signature from multi-line to single-line in apps/web/lib/api/domains/jira-api.ts. No logic change.

All prior findings from every review round remain resolved.


Findings

No findings. No new logic was introduced.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge. This round is a formatting-only commit; no new issues introduced.

@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: 5

Caution

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

⚠️ Outside diff range comments (3)
apps/web/components/linear/linear-issue-watch-fields.tsx (1)

50-107: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Scope the per-team cache by workspaceId.

clearWorkspaceScopedForm keeps teamKey, so switching workspaces can hit fetchedTeams.has(teamKey) and reuse the previous workspace’s states/labels/users for that key. Key these caches by ${workspaceId}:${teamKey} or clear them when workspaceId changes.

🤖 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 `@apps/web/components/linear/linear-issue-watch-fields.tsx` around lines 50 -
107, The per-team fetch cache in the useEffect for linear-issue-watch-fields.tsx
is only keyed by teamKey, so switching workspaces can incorrectly reuse stale
states/labels/users. Update the cache logic around fetchedTeams and the
listLinearStates/listLinearLabels/listLinearUsers loading flow to scope entries
by workspaceId as well, using a composite key like workspaceId:teamKey or
clearing the cache when workspaceId changes. Make sure the cleanup and
fetched.has/fetched.add/fetched.delete paths all use the same workspace-scoped
key.
apps/web/components/sentry/sentry-issue-dialog.tsx (1)

176-208: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset Sentry dialog state when workspaceId changes. The dialog keeps loaded, projects, issues, and filter in local state, but only clears them when open becomes false. If the workspace switches while the dialog stays mounted, the old workspace’s projects and search results remain visible, and the next search can run with stale filters. Clear that state on workspaceId changes, or remount the dialog per workspace.

🤖 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 `@apps/web/components/sentry/sentry-issue-dialog.tsx` around lines 176 - 208,
Reset the Sentry dialog state when the active workspace changes, not just when
`open` becomes false. Update the `useEffect` in `SentryIssueDialog` so a
`workspaceId` switch clears `loaded`, `projects`, `issues`, and `filter` before
fetching again, or otherwise force the dialog to remount per workspace. Make
sure the reset logic is tied to the existing state setters (`setLoaded`,
`setProjects`, `setIssues`, `setFilter`) so stale data and filters from the
previous workspace cannot persist.
apps/backend/internal/linear/handlers.go (1)

58-211: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Add a workspace ownership gate before using workspace_id. In apps/backend/internal/linear/handlers.go:34-45,58-201, these routes take ctx.Query("workspace_id") directly and mount without any auth/ownership middleware. That leaves Linear config reads/writes, secret-backed connection tests, and workspace-scoped team/state/label/user searches controlled entirely by a caller-supplied query param. Add caller/workspace validation here or behind shared middleware.

🤖 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 `@apps/backend/internal/linear/handlers.go` around lines 58 - 211, The Linear
handlers currently trust ctx.Query("workspace_id") via workspaceID and expose
config, test, and workspace-scoped list/search endpoints without any ownership
check. Add a caller/workspace validation gate before any use of workspaceID in
Controller methods like httpGetConfig, httpSetConfig, httpDeleteConfig,
httpTestConfig, httpListTeams, httpListStates, httpListLabels, httpListUsers,
and httpSearchIssues, or move the check into shared middleware/service
authorization. Ensure unauthorized callers cannot access or mutate data just by
supplying a workspace_id query param.
🧹 Nitpick comments (6)
apps/backend/internal/slack/models.go (1)

135-143: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

LegacySecretKeyFor wrappers are redundant.* No in-repo callers use them, and they just forward to SecretKeyForToken/SecretKeyForCookie. Drop them unless you still need external compatibility.

🤖 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 `@apps/backend/internal/slack/models.go` around lines 135 - 143, The
LegacySecretKeyForToken and LegacySecretKeyForCookie wrappers in models.go are
redundant passthroughs with no in-repo usage. Remove both wrapper functions and
keep the existing SecretKeyForToken and SecretKeyForCookie helpers as the single
source of truth, unless there is a confirmed external compatibility requirement.

Source: Linters/SAST tools

apps/web/app/jira/jira-page-client.tsx (1)

73-111: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Stale loaded/configured/projects during workspace switch.

When workspaceId changes, the effect re-fetches but never resets loaded to false first, so the previous workspace's configured/projects/defaultProjectKey remain visible until the new fetch resolves, rather than showing the "Checking Jira status…" state. Combined with the missing remount above, this can briefly (or, for filters, persistently) display data belonging to the wrong workspace.

🤖 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 `@apps/web/app/jira/jira-page-client.tsx` around lines 73 - 111, Reset the Jira
state when useJiraPageData runs for a new workspaceId so stale values from the
previous workspace are not shown. In the useEffect inside useJiraPageData, clear
loaded, configured, projects, and defaultProjectKey before starting the async
load, and keep the existing cancellation handling so the UI returns to the
“Checking Jira status…” state while getJiraConfig and loadUserProjects resolve.
apps/backend/internal/linear/service.go (1)

278-291: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential per-workspace auth-health probing could stall the health-check cycle.

RecordAuthHealth loops over every configured workspace and calls RecordAuthHealthForWorkspace synchronously, each of which can take up to authProbeTimeout (15s) to complete. With more than a handful of workspaces configured, one health-check tick could take minutes, delaying freshness of the "connected/disconnected" indicator for all workspaces and likely running past the poller's own tick interval.

Consider bounding concurrency (e.g. errgroup.Group with SetLimit) so probes run in parallel instead of one-at-a-time.

🤖 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 `@apps/backend/internal/linear/service.go` around lines 278 - 291,
RecordAuthHealth currently probes each workspace sequentially by calling
RecordAuthHealthForWorkspace in a loop, which can make the health-check tick run
too long. Update RecordAuthHealth to run per-workspace probes concurrently with
a bounded limit (for example using an errgroup.Group with SetLimit) so the
workspace IDs from s.store.ListConfigWorkspaceIDs are processed in parallel
without overwhelming the system. Keep the existing empty-workspace path that
calls s.fireProbeHook, and preserve the current warning behavior on
ListConfigWorkspaceIDs errors.
apps/backend/internal/sentry/service.go (2)

284-297: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential per-workspace auth-health probing.

RecordAuthHealth loops over every configured workspace and calls RecordAuthHealthForWorkspace synchronously, each with its own authProbeTimeout. With N workspaces and a slow/unreachable Sentry instance for some of them, a single health-check pass can take up to N × authProbeTimeout instead of bounding total wall time. Consider probing workspaces concurrently (bounded worker pool) so one slow/unreachable workspace doesn't delay health updates for the rest.

🤖 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 `@apps/backend/internal/sentry/service.go` around lines 284 - 297,
RecordAuthHealth currently probes each workspace one after another by calling
RecordAuthHealthForWorkspace in a loop, which can make total health-check time
scale with the number of workspaces. Update RecordAuthHealth in Service to
dispatch per-workspace probes concurrently, ideally with a bounded worker pool
or similar limit, while preserving the existing empty-workspace path and the
ListConfigWorkspaceIDs error handling.

338-397: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ListOrganizations/ListProjects/GetIssue weren't given *ForWorkspace siblings, unlike SearchIssues.

Every other capability in this diff got a public XxxForWorkspace method (GetConfigForWorkspace, SetConfigForWorkspace, DeleteConfigForWorkspace, TestConnectionForWorkspace, ProbeAuthForWorkspace, SearchIssuesForWorkspace). These three did not, so handlers.go reaches into the unexported clientFor directly to get workspace scoping (see httpListOrganizations/httpListProjects/httpGetIssue), duplicating client-resolution/error-wiring logic that already lives in the service. The unscoped versions here become effectively dead in production (only exercised by tests), which is an inconsistent API surface for future maintainers.

func (s *Service) ListOrganizationsForWorkspace(ctx context.Context, workspaceID string) ([]SentryOrganization, error) {
	workspaceID, err := s.normalizeWorkspaceID(workspaceID)
	if err != nil {
		return nil, err
	}
	client, err := s.clientFor(ctx, workspaceID)
	if err != nil {
		return nil, err
	}
	return client.ListOrganizations(ctx)
}
// ...and analogous ListProjectsForWorkspace / GetIssueForWorkspace,
// with handlers.go calling these instead of clientFor directly.
🤖 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 `@apps/backend/internal/sentry/service.go` around lines 338 - 397, Add public
workspace-scoped siblings for the unscoped Sentry APIs so the service has a
consistent surface: create ListOrganizationsForWorkspace,
ListProjectsForWorkspace, and GetIssueForWorkspace alongside
SearchIssuesForWorkspace. Each should use normalizeWorkspaceID and clientFor
inside Service, then delegate to the matching client method. Update the HTTP
handlers in handlers.go to call these new Service methods instead of reaching
into clientFor directly, so workspace resolution and error handling live in one
place.
apps/backend/internal/sentry/handlers.go (1)

96-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Client resolution duplicated across handlers instead of going through the service.

httpListOrganizations, httpListProjects, and httpGetIssue all call c.service.clientFor(ctx.Request.Context(), c.workspaceID(ctx)) directly, then invoke client methods and re-implement error handling that SearchIssuesForWorkspace already centralizes in the service. This reaches past the service's public API into an unexported implementation detail (clientFor) from three call sites, so any future change to client-resolution semantics (e.g. additional validation, metrics, or caching) must be updated in the handler layer instead of the service. See the companion comment on service.go proposing ListOrganizationsForWorkspace/ListProjectsForWorkspace/GetIssueForWorkspace.

Also applies to: 144-161

🤖 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 `@apps/backend/internal/sentry/handlers.go` around lines 96 - 122, The handler
methods are bypassing the service API by calling the unexported clientFor helper
directly and duplicating client/error handling. Update httpListOrganizations,
httpListProjects, and httpGetIssue to call the service’s workspace-level methods
instead (such as the proposed ListOrganizationsForWorkspace,
ListProjectsForWorkspace, and GetIssueForWorkspace) so client resolution and
errors stay centralized inside the service. Keep the handlers focused on
request/response wiring and remove the direct clientFor usage from Controller.
🤖 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 `@apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go`:
- Around line 1019-1037: The conditional in
Manager.restoreRecoveredFailedExecution is reading shared execution state
without synchronization, so the status/sessionInitialized/ACPSessionID check can
race with concurrent updates. Move the decision logic into a synchronized read
path by snapshotting execution.Status, execution.sessionInitialized, and
execution.ACPSessionID under executionStore.WithRLock (or an equivalent
lock-protected helper) before deciding whether to call
restoreRecoveredFailedExecution. Keep the existing
restoreRecoveredFailedExecution flow unchanged, but ensure the gate that reaches
it uses locked, consistent values.

In `@apps/backend/internal/integrations/workspacescope/workspacescope.go`:
- Around line 47-50: The error return from getWorkspaceID is inconsistent: the
tableExists/db failure path currently returns FallbackWorkspaceID with a non-nil
error, unlike the other error branches in this function that return an empty
string. Update the early return after tableExists so getWorkspaceID always
returns "" whenever err is set, matching the existing error contract and keeping
callers from mistaking a DB failure for a valid fallback workspace.

In `@apps/backend/internal/jira/service.go`:
- Around line 626-653: revealSecret currently falls back to SecretKey whenever
reading SecretKeyForWorkspace(workspaceID) fails, which can hide real
workspace-store errors and return the wrong secret source. Update revealSecret
in Service so it only tries the legacy SecretKey when the workspace secret is
actually missing/empty, and immediately return any non-missing error from
s.secrets.Reveal for SecretKeyForWorkspace. Keep the fallback behavior limited
to a true not-found case, and preserve the existing nil-secrets handling.

In `@apps/backend/internal/sentry/handlers.go`:
- Around line 159-161: The workspaceID helper currently trusts the raw
workspace_id query param, which allows selecting arbitrary workspaces. Add a
workspace-membership authorization check in the Sentry request flow before any
handler uses workspaceID from the Controller, and reject requests when the
caller is not a member of that workspace. Use the existing workspaceID(ctx)
entry point and the surrounding Sentry handler/controller methods to locate
where to enforce the check so the query param cannot be used to switch
workspaces.

In `@apps/web/app/jira/jira-page-client.tsx`:
- Around line 171-198: AuthenticatedView is preserving filter state across
workspace switches because useFilterState(defaultProjectKey) is not
re-initialized when workspaceId changes. Add a key based on workspaceId to the
AuthenticatedView component so it remounts on workspace change, ensuring
filters.projectKeys resets to the correct defaultProjectKey for the active
workspace and does not carry over stale Jira filters.

---

Outside diff comments:
In `@apps/backend/internal/linear/handlers.go`:
- Around line 58-211: The Linear handlers currently trust
ctx.Query("workspace_id") via workspaceID and expose config, test, and
workspace-scoped list/search endpoints without any ownership check. Add a
caller/workspace validation gate before any use of workspaceID in Controller
methods like httpGetConfig, httpSetConfig, httpDeleteConfig, httpTestConfig,
httpListTeams, httpListStates, httpListLabels, httpListUsers, and
httpSearchIssues, or move the check into shared middleware/service
authorization. Ensure unauthorized callers cannot access or mutate data just by
supplying a workspace_id query param.

In `@apps/web/components/linear/linear-issue-watch-fields.tsx`:
- Around line 50-107: The per-team fetch cache in the useEffect for
linear-issue-watch-fields.tsx is only keyed by teamKey, so switching workspaces
can incorrectly reuse stale states/labels/users. Update the cache logic around
fetchedTeams and the listLinearStates/listLinearLabels/listLinearUsers loading
flow to scope entries by workspaceId as well, using a composite key like
workspaceId:teamKey or clearing the cache when workspaceId changes. Make sure
the cleanup and fetched.has/fetched.add/fetched.delete paths all use the same
workspace-scoped key.

In `@apps/web/components/sentry/sentry-issue-dialog.tsx`:
- Around line 176-208: Reset the Sentry dialog state when the active workspace
changes, not just when `open` becomes false. Update the `useEffect` in
`SentryIssueDialog` so a `workspaceId` switch clears `loaded`, `projects`,
`issues`, and `filter` before fetching again, or otherwise force the dialog to
remount per workspace. Make sure the reset logic is tied to the existing state
setters (`setLoaded`, `setProjects`, `setIssues`, `setFilter`) so stale data and
filters from the previous workspace cannot persist.

---

Nitpick comments:
In `@apps/backend/internal/linear/service.go`:
- Around line 278-291: RecordAuthHealth currently probes each workspace
sequentially by calling RecordAuthHealthForWorkspace in a loop, which can make
the health-check tick run too long. Update RecordAuthHealth to run per-workspace
probes concurrently with a bounded limit (for example using an errgroup.Group
with SetLimit) so the workspace IDs from s.store.ListConfigWorkspaceIDs are
processed in parallel without overwhelming the system. Keep the existing
empty-workspace path that calls s.fireProbeHook, and preserve the current
warning behavior on ListConfigWorkspaceIDs errors.

In `@apps/backend/internal/sentry/handlers.go`:
- Around line 96-122: The handler methods are bypassing the service API by
calling the unexported clientFor helper directly and duplicating client/error
handling. Update httpListOrganizations, httpListProjects, and httpGetIssue to
call the service’s workspace-level methods instead (such as the proposed
ListOrganizationsForWorkspace, ListProjectsForWorkspace, and
GetIssueForWorkspace) so client resolution and errors stay centralized inside
the service. Keep the handlers focused on request/response wiring and remove the
direct clientFor usage from Controller.

In `@apps/backend/internal/sentry/service.go`:
- Around line 284-297: RecordAuthHealth currently probes each workspace one
after another by calling RecordAuthHealthForWorkspace in a loop, which can make
total health-check time scale with the number of workspaces. Update
RecordAuthHealth in Service to dispatch per-workspace probes concurrently,
ideally with a bounded worker pool or similar limit, while preserving the
existing empty-workspace path and the ListConfigWorkspaceIDs error handling.
- Around line 338-397: Add public workspace-scoped siblings for the unscoped
Sentry APIs so the service has a consistent surface: create
ListOrganizationsForWorkspace, ListProjectsForWorkspace, and
GetIssueForWorkspace alongside SearchIssuesForWorkspace. Each should use
normalizeWorkspaceID and clientFor inside Service, then delegate to the matching
client method. Update the HTTP handlers in handlers.go to call these new Service
methods instead of reaching into clientFor directly, so workspace resolution and
error handling live in one place.

In `@apps/backend/internal/slack/models.go`:
- Around line 135-143: The LegacySecretKeyForToken and LegacySecretKeyForCookie
wrappers in models.go are redundant passthroughs with no in-repo usage. Remove
both wrapper functions and keep the existing SecretKeyForToken and
SecretKeyForCookie helpers as the single source of truth, unless there is a
confirmed external compatibility requirement.

In `@apps/web/app/jira/jira-page-client.tsx`:
- Around line 73-111: Reset the Jira state when useJiraPageData runs for a new
workspaceId so stale values from the previous workspace are not shown. In the
useEffect inside useJiraPageData, clear loaded, configured, projects, and
defaultProjectKey before starting the async load, and keep the existing
cancellation handling so the UI returns to the “Checking Jira status…” state
while getJiraConfig and loadUserProjects resolve.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a85a7c5b-20d3-494d-a75a-a80f5fe30a2b

📥 Commits

Reviewing files that changed from the base of the PR and between f8c659a and 3699460.

📒 Files selected for processing (114)
  • apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go
  • apps/backend/internal/backendapp/e2e_reset.go
  • apps/backend/internal/github/controller.go
  • apps/backend/internal/github/handlers.go
  • apps/backend/internal/github/models.go
  • apps/backend/internal/github/service_cleanup.go
  • apps/backend/internal/github/service_issues.go
  • apps/backend/internal/github/service_reviews.go
  • apps/backend/internal/github/store.go
  • apps/backend/internal/github/workspace_settings_service.go
  • apps/backend/internal/github/workspace_settings_test.go
  • apps/backend/internal/integrations/workspacescope/workspacescope.go
  • apps/backend/internal/integrations/workspacescope/workspacescope_test.go
  • apps/backend/internal/jira/handlers.go
  • apps/backend/internal/jira/models.go
  • apps/backend/internal/jira/poller_issue_watch_test.go
  • apps/backend/internal/jira/poller_test.go
  • apps/backend/internal/jira/provider.go
  • apps/backend/internal/jira/service.go
  • apps/backend/internal/jira/service_issue_watch_test.go
  • apps/backend/internal/jira/service_test.go
  • apps/backend/internal/jira/store.go
  • apps/backend/internal/jira/store_test.go
  • apps/backend/internal/linear/handlers.go
  • apps/backend/internal/linear/models.go
  • apps/backend/internal/linear/poller_issue_watch_test.go
  • apps/backend/internal/linear/poller_test.go
  • apps/backend/internal/linear/provider.go
  • apps/backend/internal/linear/service.go
  • apps/backend/internal/linear/service_issue_watch.go
  • apps/backend/internal/linear/service_issue_watch_test.go
  • apps/backend/internal/linear/service_test.go
  • apps/backend/internal/linear/store.go
  • apps/backend/internal/linear/store_test.go
  • apps/backend/internal/sentry/handlers.go
  • apps/backend/internal/sentry/models.go
  • apps/backend/internal/sentry/poller_issue_watch_test.go
  • apps/backend/internal/sentry/poller_test.go
  • apps/backend/internal/sentry/provider.go
  • apps/backend/internal/sentry/service.go
  • apps/backend/internal/sentry/service_issue_watch.go
  • apps/backend/internal/sentry/service_issue_watch_test.go
  • apps/backend/internal/sentry/service_test.go
  • apps/backend/internal/sentry/store.go
  • apps/backend/internal/sentry/store_test.go
  • apps/backend/internal/slack/handlers.go
  • apps/backend/internal/slack/models.go
  • apps/backend/internal/slack/provider.go
  • apps/backend/internal/slack/service.go
  • apps/backend/internal/slack/store.go
  • apps/backend/internal/slack/store_test.go
  • apps/backend/internal/slack/trigger.go
  • apps/backend/internal/slack/trigger_test.go
  • apps/web/app/github/github-page-client.tsx
  • apps/web/app/jira/jira-page-client.tsx
  • apps/web/app/linear/linear-page-client.tsx
  • apps/web/components/app-sidebar/app-sidebar-workspace-picker.test.tsx
  • apps/web/components/app-sidebar/app-sidebar-workspace-picker.tsx
  • apps/web/components/github/default-queries-section.tsx
  • apps/web/components/github/github-repo-scope-section.tsx
  • apps/web/components/github/github-settings.tsx
  • apps/web/components/github/issue-watch-dialog.tsx
  • apps/web/components/github/my-github/use-default-query-presets.test.ts
  • apps/web/components/github/my-github/use-default-query-presets.ts
  • apps/web/components/github/my-github/use-github-search.ts
  • apps/web/components/github/my-github/use-saved-presets.test.ts
  • apps/web/components/github/my-github/use-saved-presets.ts
  • apps/web/components/github/repo-filter-selector.tsx
  • apps/web/components/github/review-watch-dialog.tsx
  • apps/web/components/integrations/workspace-scoped-section.tsx
  • apps/web/components/jira/jira-import-bar.tsx
  • apps/web/components/jira/jira-issue-watch-dialog.tsx
  • apps/web/components/jira/jira-link-button.tsx
  • apps/web/components/jira/jira-settings.tsx
  • apps/web/components/jira/jira-ticket-common.tsx
  • apps/web/components/jira/my-jira/use-jira-search.ts
  • apps/web/components/linear/linear-import-bar.tsx
  • apps/web/components/linear/linear-issue-common.tsx
  • apps/web/components/linear/linear-issue-watch-dialog.tsx
  • apps/web/components/linear/linear-issue-watch-fields.tsx
  • apps/web/components/linear/linear-link-button.tsx
  • apps/web/components/linear/linear-settings.tsx
  • apps/web/components/sentry/sentry-issue-button.tsx
  • apps/web/components/sentry/sentry-issue-dialog.tsx
  • apps/web/components/sentry/sentry-issue-watch-dialog.tsx
  • apps/web/components/sentry/sentry-link-button.tsx
  • apps/web/components/sentry/sentry-settings.tsx
  • apps/web/components/settings/settings-layout-client.tsx
  • apps/web/components/slack/slack-settings.tsx
  • apps/web/components/task/task-top-bar.tsx
  • apps/web/components/task/workspace-switcher.tsx
  • apps/web/e2e/helpers/api-client.ts
  • apps/web/e2e/pages/linear-settings-page.ts
  • apps/web/e2e/tests/integrations/github-workspace-settings.spec.ts
  • apps/web/e2e/tests/integrations/linear-settings.spec.ts
  • apps/web/e2e/tests/integrations/mobile-linear-settings.spec.ts
  • apps/web/e2e/tests/pr/pr-watcher-cleanup-policy.spec.ts
  • apps/web/hooks/domains/github/use-issue-watches.ts
  • apps/web/hooks/domains/github/use-review-watches.ts
  • apps/web/hooks/domains/jira/use-jira-availability.ts
  • apps/web/hooks/domains/linear/use-linear-availability.ts
  • apps/web/hooks/domains/sentry/use-sentry-availability.ts
  • apps/web/hooks/domains/slack/use-slack-availability.ts
  • apps/web/lib/api/domains/github-api.ts
  • apps/web/lib/api/domains/jira-api.ts
  • apps/web/lib/api/domains/linear-api.test.ts
  • apps/web/lib/api/domains/linear-api.ts
  • apps/web/lib/api/domains/sentry-api.ts
  • apps/web/lib/api/domains/slack-api.ts
  • apps/web/lib/types/github.ts
  • apps/web/lib/types/jira.ts
  • apps/web/lib/types/linear.ts
  • apps/web/lib/types/sentry.ts
  • apps/web/lib/types/slack.ts
💤 Files with no reviewable changes (25)
  • apps/web/e2e/tests/integrations/mobile-linear-settings.spec.ts
  • apps/web/lib/types/slack.ts
  • apps/web/lib/types/sentry.ts
  • apps/web/e2e/tests/integrations/linear-settings.spec.ts
  • apps/web/lib/types/linear.ts
  • apps/web/lib/api/domains/linear-api.test.ts
  • apps/web/e2e/pages/linear-settings-page.ts
  • apps/web/lib/types/jira.ts
  • apps/web/e2e/tests/integrations/github-workspace-settings.spec.ts
  • apps/web/hooks/domains/linear/use-linear-availability.ts
  • apps/web/components/task/task-top-bar.tsx
  • apps/web/e2e/tests/pr/pr-watcher-cleanup-policy.spec.ts
  • apps/web/hooks/domains/slack/use-slack-availability.ts
  • apps/web/hooks/domains/github/use-issue-watches.ts
  • apps/web/hooks/domains/sentry/use-sentry-availability.ts
  • apps/web/lib/types/github.ts
  • apps/web/components/task/workspace-switcher.tsx
  • apps/web/hooks/domains/jira/use-jira-availability.ts
  • apps/web/hooks/domains/github/use-review-watches.ts
  • apps/web/lib/api/domains/sentry-api.ts
  • apps/web/lib/api/domains/slack-api.ts
  • apps/web/lib/api/domains/linear-api.ts
  • apps/web/e2e/helpers/api-client.ts
  • apps/web/lib/api/domains/jira-api.ts
  • apps/web/lib/api/domains/github-api.ts
✅ Files skipped from review due to trivial changes (3)
  • apps/web/components/app-sidebar/app-sidebar-workspace-picker.tsx
  • apps/web/components/jira/jira-import-bar.tsx
  • apps/backend/internal/jira/service_test.go
🚧 Files skipped from review as they are similar to previous changes (24)
  • apps/web/components/github/review-watch-dialog.tsx
  • apps/web/components/integrations/workspace-scoped-section.tsx
  • apps/web/components/github/issue-watch-dialog.tsx
  • apps/web/components/github/default-queries-section.tsx
  • apps/backend/internal/backendapp/e2e_reset.go
  • apps/backend/internal/github/service_issues.go
  • apps/backend/internal/github/service_reviews.go
  • apps/web/components/github/my-github/use-saved-presets.test.ts
  • apps/backend/internal/github/service_cleanup.go
  • apps/web/components/github/my-github/use-default-query-presets.test.ts
  • apps/web/components/github/my-github/use-saved-presets.ts
  • apps/web/components/github/my-github/use-github-search.ts
  • apps/web/components/github/github-repo-scope-section.tsx
  • apps/backend/internal/github/handlers.go
  • apps/backend/internal/github/workspace_settings_service.go
  • apps/web/components/settings/settings-layout-client.tsx
  • apps/web/components/github/my-github/use-default-query-presets.ts
  • apps/backend/internal/github/models.go
  • apps/web/app/github/github-page-client.tsx
  • apps/backend/internal/github/workspace_settings_test.go
  • apps/web/components/github/github-settings.tsx
  • apps/web/components/github/repo-filter-selector.tsx
  • apps/backend/internal/github/store.go
  • apps/backend/internal/github/controller.go

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (3)
apps/web/components/linear/linear-issue-watch-fields.tsx (1)

50-107: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Scope the per-team cache by workspaceId.

clearWorkspaceScopedForm keeps teamKey, so switching workspaces can hit fetchedTeams.has(teamKey) and reuse the previous workspace’s states/labels/users for that key. Key these caches by ${workspaceId}:${teamKey} or clear them when workspaceId changes.

🤖 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 `@apps/web/components/linear/linear-issue-watch-fields.tsx` around lines 50 -
107, The per-team fetch cache in the useEffect for linear-issue-watch-fields.tsx
is only keyed by teamKey, so switching workspaces can incorrectly reuse stale
states/labels/users. Update the cache logic around fetchedTeams and the
listLinearStates/listLinearLabels/listLinearUsers loading flow to scope entries
by workspaceId as well, using a composite key like workspaceId:teamKey or
clearing the cache when workspaceId changes. Make sure the cleanup and
fetched.has/fetched.add/fetched.delete paths all use the same workspace-scoped
key.
apps/web/components/sentry/sentry-issue-dialog.tsx (1)

176-208: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset Sentry dialog state when workspaceId changes. The dialog keeps loaded, projects, issues, and filter in local state, but only clears them when open becomes false. If the workspace switches while the dialog stays mounted, the old workspace’s projects and search results remain visible, and the next search can run with stale filters. Clear that state on workspaceId changes, or remount the dialog per workspace.

🤖 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 `@apps/web/components/sentry/sentry-issue-dialog.tsx` around lines 176 - 208,
Reset the Sentry dialog state when the active workspace changes, not just when
`open` becomes false. Update the `useEffect` in `SentryIssueDialog` so a
`workspaceId` switch clears `loaded`, `projects`, `issues`, and `filter` before
fetching again, or otherwise force the dialog to remount per workspace. Make
sure the reset logic is tied to the existing state setters (`setLoaded`,
`setProjects`, `setIssues`, `setFilter`) so stale data and filters from the
previous workspace cannot persist.
apps/backend/internal/linear/handlers.go (1)

58-211: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Add a workspace ownership gate before using workspace_id. In apps/backend/internal/linear/handlers.go:34-45,58-201, these routes take ctx.Query("workspace_id") directly and mount without any auth/ownership middleware. That leaves Linear config reads/writes, secret-backed connection tests, and workspace-scoped team/state/label/user searches controlled entirely by a caller-supplied query param. Add caller/workspace validation here or behind shared middleware.

🤖 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 `@apps/backend/internal/linear/handlers.go` around lines 58 - 211, The Linear
handlers currently trust ctx.Query("workspace_id") via workspaceID and expose
config, test, and workspace-scoped list/search endpoints without any ownership
check. Add a caller/workspace validation gate before any use of workspaceID in
Controller methods like httpGetConfig, httpSetConfig, httpDeleteConfig,
httpTestConfig, httpListTeams, httpListStates, httpListLabels, httpListUsers,
and httpSearchIssues, or move the check into shared middleware/service
authorization. Ensure unauthorized callers cannot access or mutate data just by
supplying a workspace_id query param.
🧹 Nitpick comments (6)
apps/backend/internal/slack/models.go (1)

135-143: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

LegacySecretKeyFor wrappers are redundant.* No in-repo callers use them, and they just forward to SecretKeyForToken/SecretKeyForCookie. Drop them unless you still need external compatibility.

🤖 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 `@apps/backend/internal/slack/models.go` around lines 135 - 143, The
LegacySecretKeyForToken and LegacySecretKeyForCookie wrappers in models.go are
redundant passthroughs with no in-repo usage. Remove both wrapper functions and
keep the existing SecretKeyForToken and SecretKeyForCookie helpers as the single
source of truth, unless there is a confirmed external compatibility requirement.

Source: Linters/SAST tools

apps/web/app/jira/jira-page-client.tsx (1)

73-111: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Stale loaded/configured/projects during workspace switch.

When workspaceId changes, the effect re-fetches but never resets loaded to false first, so the previous workspace's configured/projects/defaultProjectKey remain visible until the new fetch resolves, rather than showing the "Checking Jira status…" state. Combined with the missing remount above, this can briefly (or, for filters, persistently) display data belonging to the wrong workspace.

🤖 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 `@apps/web/app/jira/jira-page-client.tsx` around lines 73 - 111, Reset the Jira
state when useJiraPageData runs for a new workspaceId so stale values from the
previous workspace are not shown. In the useEffect inside useJiraPageData, clear
loaded, configured, projects, and defaultProjectKey before starting the async
load, and keep the existing cancellation handling so the UI returns to the
“Checking Jira status…” state while getJiraConfig and loadUserProjects resolve.
apps/backend/internal/linear/service.go (1)

278-291: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential per-workspace auth-health probing could stall the health-check cycle.

RecordAuthHealth loops over every configured workspace and calls RecordAuthHealthForWorkspace synchronously, each of which can take up to authProbeTimeout (15s) to complete. With more than a handful of workspaces configured, one health-check tick could take minutes, delaying freshness of the "connected/disconnected" indicator for all workspaces and likely running past the poller's own tick interval.

Consider bounding concurrency (e.g. errgroup.Group with SetLimit) so probes run in parallel instead of one-at-a-time.

🤖 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 `@apps/backend/internal/linear/service.go` around lines 278 - 291,
RecordAuthHealth currently probes each workspace sequentially by calling
RecordAuthHealthForWorkspace in a loop, which can make the health-check tick run
too long. Update RecordAuthHealth to run per-workspace probes concurrently with
a bounded limit (for example using an errgroup.Group with SetLimit) so the
workspace IDs from s.store.ListConfigWorkspaceIDs are processed in parallel
without overwhelming the system. Keep the existing empty-workspace path that
calls s.fireProbeHook, and preserve the current warning behavior on
ListConfigWorkspaceIDs errors.
apps/backend/internal/sentry/service.go (2)

284-297: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential per-workspace auth-health probing.

RecordAuthHealth loops over every configured workspace and calls RecordAuthHealthForWorkspace synchronously, each with its own authProbeTimeout. With N workspaces and a slow/unreachable Sentry instance for some of them, a single health-check pass can take up to N × authProbeTimeout instead of bounding total wall time. Consider probing workspaces concurrently (bounded worker pool) so one slow/unreachable workspace doesn't delay health updates for the rest.

🤖 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 `@apps/backend/internal/sentry/service.go` around lines 284 - 297,
RecordAuthHealth currently probes each workspace one after another by calling
RecordAuthHealthForWorkspace in a loop, which can make total health-check time
scale with the number of workspaces. Update RecordAuthHealth in Service to
dispatch per-workspace probes concurrently, ideally with a bounded worker pool
or similar limit, while preserving the existing empty-workspace path and the
ListConfigWorkspaceIDs error handling.

338-397: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ListOrganizations/ListProjects/GetIssue weren't given *ForWorkspace siblings, unlike SearchIssues.

Every other capability in this diff got a public XxxForWorkspace method (GetConfigForWorkspace, SetConfigForWorkspace, DeleteConfigForWorkspace, TestConnectionForWorkspace, ProbeAuthForWorkspace, SearchIssuesForWorkspace). These three did not, so handlers.go reaches into the unexported clientFor directly to get workspace scoping (see httpListOrganizations/httpListProjects/httpGetIssue), duplicating client-resolution/error-wiring logic that already lives in the service. The unscoped versions here become effectively dead in production (only exercised by tests), which is an inconsistent API surface for future maintainers.

func (s *Service) ListOrganizationsForWorkspace(ctx context.Context, workspaceID string) ([]SentryOrganization, error) {
	workspaceID, err := s.normalizeWorkspaceID(workspaceID)
	if err != nil {
		return nil, err
	}
	client, err := s.clientFor(ctx, workspaceID)
	if err != nil {
		return nil, err
	}
	return client.ListOrganizations(ctx)
}
// ...and analogous ListProjectsForWorkspace / GetIssueForWorkspace,
// with handlers.go calling these instead of clientFor directly.
🤖 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 `@apps/backend/internal/sentry/service.go` around lines 338 - 397, Add public
workspace-scoped siblings for the unscoped Sentry APIs so the service has a
consistent surface: create ListOrganizationsForWorkspace,
ListProjectsForWorkspace, and GetIssueForWorkspace alongside
SearchIssuesForWorkspace. Each should use normalizeWorkspaceID and clientFor
inside Service, then delegate to the matching client method. Update the HTTP
handlers in handlers.go to call these new Service methods instead of reaching
into clientFor directly, so workspace resolution and error handling live in one
place.
apps/backend/internal/sentry/handlers.go (1)

96-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Client resolution duplicated across handlers instead of going through the service.

httpListOrganizations, httpListProjects, and httpGetIssue all call c.service.clientFor(ctx.Request.Context(), c.workspaceID(ctx)) directly, then invoke client methods and re-implement error handling that SearchIssuesForWorkspace already centralizes in the service. This reaches past the service's public API into an unexported implementation detail (clientFor) from three call sites, so any future change to client-resolution semantics (e.g. additional validation, metrics, or caching) must be updated in the handler layer instead of the service. See the companion comment on service.go proposing ListOrganizationsForWorkspace/ListProjectsForWorkspace/GetIssueForWorkspace.

Also applies to: 144-161

🤖 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 `@apps/backend/internal/sentry/handlers.go` around lines 96 - 122, The handler
methods are bypassing the service API by calling the unexported clientFor helper
directly and duplicating client/error handling. Update httpListOrganizations,
httpListProjects, and httpGetIssue to call the service’s workspace-level methods
instead (such as the proposed ListOrganizationsForWorkspace,
ListProjectsForWorkspace, and GetIssueForWorkspace) so client resolution and
errors stay centralized inside the service. Keep the handlers focused on
request/response wiring and remove the direct clientFor usage from Controller.
🤖 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 `@apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go`:
- Around line 1019-1037: The conditional in
Manager.restoreRecoveredFailedExecution is reading shared execution state
without synchronization, so the status/sessionInitialized/ACPSessionID check can
race with concurrent updates. Move the decision logic into a synchronized read
path by snapshotting execution.Status, execution.sessionInitialized, and
execution.ACPSessionID under executionStore.WithRLock (or an equivalent
lock-protected helper) before deciding whether to call
restoreRecoveredFailedExecution. Keep the existing
restoreRecoveredFailedExecution flow unchanged, but ensure the gate that reaches
it uses locked, consistent values.

In `@apps/backend/internal/integrations/workspacescope/workspacescope.go`:
- Around line 47-50: The error return from getWorkspaceID is inconsistent: the
tableExists/db failure path currently returns FallbackWorkspaceID with a non-nil
error, unlike the other error branches in this function that return an empty
string. Update the early return after tableExists so getWorkspaceID always
returns "" whenever err is set, matching the existing error contract and keeping
callers from mistaking a DB failure for a valid fallback workspace.

In `@apps/backend/internal/jira/service.go`:
- Around line 626-653: revealSecret currently falls back to SecretKey whenever
reading SecretKeyForWorkspace(workspaceID) fails, which can hide real
workspace-store errors and return the wrong secret source. Update revealSecret
in Service so it only tries the legacy SecretKey when the workspace secret is
actually missing/empty, and immediately return any non-missing error from
s.secrets.Reveal for SecretKeyForWorkspace. Keep the fallback behavior limited
to a true not-found case, and preserve the existing nil-secrets handling.

In `@apps/backend/internal/sentry/handlers.go`:
- Around line 159-161: The workspaceID helper currently trusts the raw
workspace_id query param, which allows selecting arbitrary workspaces. Add a
workspace-membership authorization check in the Sentry request flow before any
handler uses workspaceID from the Controller, and reject requests when the
caller is not a member of that workspace. Use the existing workspaceID(ctx)
entry point and the surrounding Sentry handler/controller methods to locate
where to enforce the check so the query param cannot be used to switch
workspaces.

In `@apps/web/app/jira/jira-page-client.tsx`:
- Around line 171-198: AuthenticatedView is preserving filter state across
workspace switches because useFilterState(defaultProjectKey) is not
re-initialized when workspaceId changes. Add a key based on workspaceId to the
AuthenticatedView component so it remounts on workspace change, ensuring
filters.projectKeys resets to the correct defaultProjectKey for the active
workspace and does not carry over stale Jira filters.

---

Outside diff comments:
In `@apps/backend/internal/linear/handlers.go`:
- Around line 58-211: The Linear handlers currently trust
ctx.Query("workspace_id") via workspaceID and expose config, test, and
workspace-scoped list/search endpoints without any ownership check. Add a
caller/workspace validation gate before any use of workspaceID in Controller
methods like httpGetConfig, httpSetConfig, httpDeleteConfig, httpTestConfig,
httpListTeams, httpListStates, httpListLabels, httpListUsers, and
httpSearchIssues, or move the check into shared middleware/service
authorization. Ensure unauthorized callers cannot access or mutate data just by
supplying a workspace_id query param.

In `@apps/web/components/linear/linear-issue-watch-fields.tsx`:
- Around line 50-107: The per-team fetch cache in the useEffect for
linear-issue-watch-fields.tsx is only keyed by teamKey, so switching workspaces
can incorrectly reuse stale states/labels/users. Update the cache logic around
fetchedTeams and the listLinearStates/listLinearLabels/listLinearUsers loading
flow to scope entries by workspaceId as well, using a composite key like
workspaceId:teamKey or clearing the cache when workspaceId changes. Make sure
the cleanup and fetched.has/fetched.add/fetched.delete paths all use the same
workspace-scoped key.

In `@apps/web/components/sentry/sentry-issue-dialog.tsx`:
- Around line 176-208: Reset the Sentry dialog state when the active workspace
changes, not just when `open` becomes false. Update the `useEffect` in
`SentryIssueDialog` so a `workspaceId` switch clears `loaded`, `projects`,
`issues`, and `filter` before fetching again, or otherwise force the dialog to
remount per workspace. Make sure the reset logic is tied to the existing state
setters (`setLoaded`, `setProjects`, `setIssues`, `setFilter`) so stale data and
filters from the previous workspace cannot persist.

---

Nitpick comments:
In `@apps/backend/internal/linear/service.go`:
- Around line 278-291: RecordAuthHealth currently probes each workspace
sequentially by calling RecordAuthHealthForWorkspace in a loop, which can make
the health-check tick run too long. Update RecordAuthHealth to run per-workspace
probes concurrently with a bounded limit (for example using an errgroup.Group
with SetLimit) so the workspace IDs from s.store.ListConfigWorkspaceIDs are
processed in parallel without overwhelming the system. Keep the existing
empty-workspace path that calls s.fireProbeHook, and preserve the current
warning behavior on ListConfigWorkspaceIDs errors.

In `@apps/backend/internal/sentry/handlers.go`:
- Around line 96-122: The handler methods are bypassing the service API by
calling the unexported clientFor helper directly and duplicating client/error
handling. Update httpListOrganizations, httpListProjects, and httpGetIssue to
call the service’s workspace-level methods instead (such as the proposed
ListOrganizationsForWorkspace, ListProjectsForWorkspace, and
GetIssueForWorkspace) so client resolution and errors stay centralized inside
the service. Keep the handlers focused on request/response wiring and remove the
direct clientFor usage from Controller.

In `@apps/backend/internal/sentry/service.go`:
- Around line 284-297: RecordAuthHealth currently probes each workspace one
after another by calling RecordAuthHealthForWorkspace in a loop, which can make
total health-check time scale with the number of workspaces. Update
RecordAuthHealth in Service to dispatch per-workspace probes concurrently,
ideally with a bounded worker pool or similar limit, while preserving the
existing empty-workspace path and the ListConfigWorkspaceIDs error handling.
- Around line 338-397: Add public workspace-scoped siblings for the unscoped
Sentry APIs so the service has a consistent surface: create
ListOrganizationsForWorkspace, ListProjectsForWorkspace, and
GetIssueForWorkspace alongside SearchIssuesForWorkspace. Each should use
normalizeWorkspaceID and clientFor inside Service, then delegate to the matching
client method. Update the HTTP handlers in handlers.go to call these new Service
methods instead of reaching into clientFor directly, so workspace resolution and
error handling live in one place.

In `@apps/backend/internal/slack/models.go`:
- Around line 135-143: The LegacySecretKeyForToken and LegacySecretKeyForCookie
wrappers in models.go are redundant passthroughs with no in-repo usage. Remove
both wrapper functions and keep the existing SecretKeyForToken and
SecretKeyForCookie helpers as the single source of truth, unless there is a
confirmed external compatibility requirement.

In `@apps/web/app/jira/jira-page-client.tsx`:
- Around line 73-111: Reset the Jira state when useJiraPageData runs for a new
workspaceId so stale values from the previous workspace are not shown. In the
useEffect inside useJiraPageData, clear loaded, configured, projects, and
defaultProjectKey before starting the async load, and keep the existing
cancellation handling so the UI returns to the “Checking Jira status…” state
while getJiraConfig and loadUserProjects resolve.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a85a7c5b-20d3-494d-a75a-a80f5fe30a2b

📥 Commits

Reviewing files that changed from the base of the PR and between f8c659a and 3699460.

📒 Files selected for processing (114)
  • apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go
  • apps/backend/internal/backendapp/e2e_reset.go
  • apps/backend/internal/github/controller.go
  • apps/backend/internal/github/handlers.go
  • apps/backend/internal/github/models.go
  • apps/backend/internal/github/service_cleanup.go
  • apps/backend/internal/github/service_issues.go
  • apps/backend/internal/github/service_reviews.go
  • apps/backend/internal/github/store.go
  • apps/backend/internal/github/workspace_settings_service.go
  • apps/backend/internal/github/workspace_settings_test.go
  • apps/backend/internal/integrations/workspacescope/workspacescope.go
  • apps/backend/internal/integrations/workspacescope/workspacescope_test.go
  • apps/backend/internal/jira/handlers.go
  • apps/backend/internal/jira/models.go
  • apps/backend/internal/jira/poller_issue_watch_test.go
  • apps/backend/internal/jira/poller_test.go
  • apps/backend/internal/jira/provider.go
  • apps/backend/internal/jira/service.go
  • apps/backend/internal/jira/service_issue_watch_test.go
  • apps/backend/internal/jira/service_test.go
  • apps/backend/internal/jira/store.go
  • apps/backend/internal/jira/store_test.go
  • apps/backend/internal/linear/handlers.go
  • apps/backend/internal/linear/models.go
  • apps/backend/internal/linear/poller_issue_watch_test.go
  • apps/backend/internal/linear/poller_test.go
  • apps/backend/internal/linear/provider.go
  • apps/backend/internal/linear/service.go
  • apps/backend/internal/linear/service_issue_watch.go
  • apps/backend/internal/linear/service_issue_watch_test.go
  • apps/backend/internal/linear/service_test.go
  • apps/backend/internal/linear/store.go
  • apps/backend/internal/linear/store_test.go
  • apps/backend/internal/sentry/handlers.go
  • apps/backend/internal/sentry/models.go
  • apps/backend/internal/sentry/poller_issue_watch_test.go
  • apps/backend/internal/sentry/poller_test.go
  • apps/backend/internal/sentry/provider.go
  • apps/backend/internal/sentry/service.go
  • apps/backend/internal/sentry/service_issue_watch.go
  • apps/backend/internal/sentry/service_issue_watch_test.go
  • apps/backend/internal/sentry/service_test.go
  • apps/backend/internal/sentry/store.go
  • apps/backend/internal/sentry/store_test.go
  • apps/backend/internal/slack/handlers.go
  • apps/backend/internal/slack/models.go
  • apps/backend/internal/slack/provider.go
  • apps/backend/internal/slack/service.go
  • apps/backend/internal/slack/store.go
  • apps/backend/internal/slack/store_test.go
  • apps/backend/internal/slack/trigger.go
  • apps/backend/internal/slack/trigger_test.go
  • apps/web/app/github/github-page-client.tsx
  • apps/web/app/jira/jira-page-client.tsx
  • apps/web/app/linear/linear-page-client.tsx
  • apps/web/components/app-sidebar/app-sidebar-workspace-picker.test.tsx
  • apps/web/components/app-sidebar/app-sidebar-workspace-picker.tsx
  • apps/web/components/github/default-queries-section.tsx
  • apps/web/components/github/github-repo-scope-section.tsx
  • apps/web/components/github/github-settings.tsx
  • apps/web/components/github/issue-watch-dialog.tsx
  • apps/web/components/github/my-github/use-default-query-presets.test.ts
  • apps/web/components/github/my-github/use-default-query-presets.ts
  • apps/web/components/github/my-github/use-github-search.ts
  • apps/web/components/github/my-github/use-saved-presets.test.ts
  • apps/web/components/github/my-github/use-saved-presets.ts
  • apps/web/components/github/repo-filter-selector.tsx
  • apps/web/components/github/review-watch-dialog.tsx
  • apps/web/components/integrations/workspace-scoped-section.tsx
  • apps/web/components/jira/jira-import-bar.tsx
  • apps/web/components/jira/jira-issue-watch-dialog.tsx
  • apps/web/components/jira/jira-link-button.tsx
  • apps/web/components/jira/jira-settings.tsx
  • apps/web/components/jira/jira-ticket-common.tsx
  • apps/web/components/jira/my-jira/use-jira-search.ts
  • apps/web/components/linear/linear-import-bar.tsx
  • apps/web/components/linear/linear-issue-common.tsx
  • apps/web/components/linear/linear-issue-watch-dialog.tsx
  • apps/web/components/linear/linear-issue-watch-fields.tsx
  • apps/web/components/linear/linear-link-button.tsx
  • apps/web/components/linear/linear-settings.tsx
  • apps/web/components/sentry/sentry-issue-button.tsx
  • apps/web/components/sentry/sentry-issue-dialog.tsx
  • apps/web/components/sentry/sentry-issue-watch-dialog.tsx
  • apps/web/components/sentry/sentry-link-button.tsx
  • apps/web/components/sentry/sentry-settings.tsx
  • apps/web/components/settings/settings-layout-client.tsx
  • apps/web/components/slack/slack-settings.tsx
  • apps/web/components/task/task-top-bar.tsx
  • apps/web/components/task/workspace-switcher.tsx
  • apps/web/e2e/helpers/api-client.ts
  • apps/web/e2e/pages/linear-settings-page.ts
  • apps/web/e2e/tests/integrations/github-workspace-settings.spec.ts
  • apps/web/e2e/tests/integrations/linear-settings.spec.ts
  • apps/web/e2e/tests/integrations/mobile-linear-settings.spec.ts
  • apps/web/e2e/tests/pr/pr-watcher-cleanup-policy.spec.ts
  • apps/web/hooks/domains/github/use-issue-watches.ts
  • apps/web/hooks/domains/github/use-review-watches.ts
  • apps/web/hooks/domains/jira/use-jira-availability.ts
  • apps/web/hooks/domains/linear/use-linear-availability.ts
  • apps/web/hooks/domains/sentry/use-sentry-availability.ts
  • apps/web/hooks/domains/slack/use-slack-availability.ts
  • apps/web/lib/api/domains/github-api.ts
  • apps/web/lib/api/domains/jira-api.ts
  • apps/web/lib/api/domains/linear-api.test.ts
  • apps/web/lib/api/domains/linear-api.ts
  • apps/web/lib/api/domains/sentry-api.ts
  • apps/web/lib/api/domains/slack-api.ts
  • apps/web/lib/types/github.ts
  • apps/web/lib/types/jira.ts
  • apps/web/lib/types/linear.ts
  • apps/web/lib/types/sentry.ts
  • apps/web/lib/types/slack.ts
💤 Files with no reviewable changes (25)
  • apps/web/e2e/tests/integrations/mobile-linear-settings.spec.ts
  • apps/web/lib/types/slack.ts
  • apps/web/lib/types/sentry.ts
  • apps/web/e2e/tests/integrations/linear-settings.spec.ts
  • apps/web/lib/types/linear.ts
  • apps/web/lib/api/domains/linear-api.test.ts
  • apps/web/e2e/pages/linear-settings-page.ts
  • apps/web/lib/types/jira.ts
  • apps/web/e2e/tests/integrations/github-workspace-settings.spec.ts
  • apps/web/hooks/domains/linear/use-linear-availability.ts
  • apps/web/components/task/task-top-bar.tsx
  • apps/web/e2e/tests/pr/pr-watcher-cleanup-policy.spec.ts
  • apps/web/hooks/domains/slack/use-slack-availability.ts
  • apps/web/hooks/domains/github/use-issue-watches.ts
  • apps/web/hooks/domains/sentry/use-sentry-availability.ts
  • apps/web/lib/types/github.ts
  • apps/web/components/task/workspace-switcher.tsx
  • apps/web/hooks/domains/jira/use-jira-availability.ts
  • apps/web/hooks/domains/github/use-review-watches.ts
  • apps/web/lib/api/domains/sentry-api.ts
  • apps/web/lib/api/domains/slack-api.ts
  • apps/web/lib/api/domains/linear-api.ts
  • apps/web/e2e/helpers/api-client.ts
  • apps/web/lib/api/domains/jira-api.ts
  • apps/web/lib/api/domains/github-api.ts
✅ Files skipped from review due to trivial changes (3)
  • apps/web/components/app-sidebar/app-sidebar-workspace-picker.tsx
  • apps/web/components/jira/jira-import-bar.tsx
  • apps/backend/internal/jira/service_test.go
🚧 Files skipped from review as they are similar to previous changes (24)
  • apps/web/components/github/review-watch-dialog.tsx
  • apps/web/components/integrations/workspace-scoped-section.tsx
  • apps/web/components/github/issue-watch-dialog.tsx
  • apps/web/components/github/default-queries-section.tsx
  • apps/backend/internal/backendapp/e2e_reset.go
  • apps/backend/internal/github/service_issues.go
  • apps/backend/internal/github/service_reviews.go
  • apps/web/components/github/my-github/use-saved-presets.test.ts
  • apps/backend/internal/github/service_cleanup.go
  • apps/web/components/github/my-github/use-default-query-presets.test.ts
  • apps/web/components/github/my-github/use-saved-presets.ts
  • apps/web/components/github/my-github/use-github-search.ts
  • apps/web/components/github/github-repo-scope-section.tsx
  • apps/backend/internal/github/handlers.go
  • apps/backend/internal/github/workspace_settings_service.go
  • apps/web/components/settings/settings-layout-client.tsx
  • apps/web/components/github/my-github/use-default-query-presets.ts
  • apps/backend/internal/github/models.go
  • apps/web/app/github/github-page-client.tsx
  • apps/backend/internal/github/workspace_settings_test.go
  • apps/web/components/github/github-settings.tsx
  • apps/web/components/github/repo-filter-selector.tsx
  • apps/backend/internal/github/store.go
  • apps/backend/internal/github/controller.go
🛑 Comments failed to post (5)
apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go (1)

1019-1037: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether AgentExecution field reads elsewhere are consistently guarded via WithLock/Get,
# and whether Status/ACPSessionID are ever mutated concurrently outside WithLock.
rg -nP 'execution\.(Status|sessionInitialized|ACPSessionID)\b' apps/backend/internal/agent/runtime/lifecycle -g '*.go' -C2

Repository: kdlbs/kandev

Length of output: 23957


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the execution store and the relevant manager methods around the flagged reads/writes.
files=(
  apps/backend/internal/agent/runtime/lifecycle/execution_store.go
  apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go
  apps/backend/internal/agent/runtime/lifecycle/session.go
  apps/backend/internal/agent/runtime/lifecycle/manager_launch.go
)
for f in "${files[@]}"; do
  echo "===== $f ====="
  ast-grep outline "$f" --view expanded || true
  echo
done

echo "===== execution_store.go:1-260 ====="
sed -n '1,260p' apps/backend/internal/agent/runtime/lifecycle/execution_store.go

echo "===== manager_interaction.go:960-1055 ====="
sed -n '960,1055p' apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go

Repository: kdlbs/kandev

Length of output: 25155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the exact locking API surface and all methods that mutate/read execution state.
rg -n "WithLock|GetBy|GetOr|UpdateStatus|sessionInitialized|ACPSessionID|Status =" apps/backend/internal/agent/runtime/lifecycle/*.go

Repository: kdlbs/kandev

Length of output: 35120


Read the execution flags under the store lock. execution.Status, execution.sessionInitialized, and execution.ACPSessionID are shared mutable fields and are written under executionStore.WithLock, so this check can race with a concurrent transition. Snapshot them under WithRLock (or another synchronized path) before deciding whether to restore the failed execution.

🤖 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 `@apps/backend/internal/agent/runtime/lifecycle/manager_interaction.go` around
lines 1019 - 1037, The conditional in Manager.restoreRecoveredFailedExecution is
reading shared execution state without synchronization, so the
status/sessionInitialized/ACPSessionID check can race with concurrent updates.
Move the decision logic into a synchronized read path by snapshotting
execution.Status, execution.sessionInitialized, and execution.ACPSessionID under
executionStore.WithRLock (or an equivalent lock-protected helper) before
deciding whether to call restoreRecoveredFailedExecution. Keep the existing
restoreRecoveredFailedExecution flow unchanged, but ensure the gate that reaches
it uses locked, consistent values.
apps/backend/internal/integrations/workspacescope/workspacescope.go (1)

47-50: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Inconsistent error contract: returns non-zero value alongside error.

Line 49 returns FallbackWorkspaceID together with a non-nil err, unlike every other error path in this function (lines 52, 62) which correctly return "". All current callers check err first, so there's no live bug, but this asymmetry is a latent trap for a migration-critical function—a future caller that inspects the value before the error would silently treat a real DB failure as "table missing" and migrate data into the wrong workspace.

🔧 Proposed fix
 	hasWorkspaces, err := tableExists(db, "workspaces")
-	if err != nil || !hasWorkspaces {
-		return FallbackWorkspaceID, err
+	if err != nil {
+		return "", err
+	}
+	if !hasWorkspaces {
+		return FallbackWorkspaceID, nil
 	}
📝 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.

	hasWorkspaces, err := tableExists(db, "workspaces")
	if err != nil {
		return "", err
	}
	if !hasWorkspaces {
		return FallbackWorkspaceID, nil
	}
🤖 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 `@apps/backend/internal/integrations/workspacescope/workspacescope.go` around
lines 47 - 50, The error return from getWorkspaceID is inconsistent: the
tableExists/db failure path currently returns FallbackWorkspaceID with a non-nil
error, unlike the other error branches in this function that return an empty
string. Update the early return after tableExists so getWorkspaceID always
returns "" whenever err is set, matching the existing error contract and keeping
callers from mistaking a DB failure for a valid fallback workspace.
apps/backend/internal/jira/service.go (1)

626-653: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for where the legacy jira SecretKey is migrated/deleted.
rg -nP -C3 'SecretKey\b' apps/backend/internal/jira
rg -nP -C3 'SecretKeyForWorkspace|secrets\.(Delete|Set|Reveal)\(' apps/backend/internal/jira/provider.go apps/backend/internal/jira/store.go

Repository: kdlbs/kandev

Length of output: 5224


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the Jira provider migration and the service call sites around secret access.
sed -n '1,140p' apps/backend/internal/jira/provider.go
printf '\n---\n'
sed -n '600,680p' apps/backend/internal/jira/service.go
printf '\n---\n'
sed -n '1,220p' apps/backend/internal/jira/service_test.go | sed -n '360,430p'
printf '\n---\n'
sed -n '1,140p' apps/backend/internal/jira/poller_test.go

Repository: kdlbs/kandev

Length of output: 10389


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all uses of revealSecret/secretExists and workspace binding logic.
rg -n -C3 'revealSecret\(|secretExists\(|SetConfigForWorkspace|DeleteConfigForWorkspace|GetConfigForWorkspace|GetConfig\(' apps/backend/internal/jira

Repository: kdlbs/kandev

Length of output: 24093


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the store migration path that reports MigratedFromWorkspace.
sed -n '1,240p' apps/backend/internal/jira/store.go
printf '\n---\n'
sed -n '240,420p' apps/backend/internal/jira/store.go

Repository: kdlbs/kandev

Length of output: 13968


Only fall back to the legacy secret when the workspace key is missing
revealSecret treats any error reading SecretKeyForWorkspace(...) as a miss, so a transient workspace-store failure can be masked by the legacy SecretKey and return the wrong credential source.

🤖 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 `@apps/backend/internal/jira/service.go` around lines 626 - 653, revealSecret
currently falls back to SecretKey whenever reading
SecretKeyForWorkspace(workspaceID) fails, which can hide real workspace-store
errors and return the wrong secret source. Update revealSecret in Service so it
only tries the legacy SecretKey when the workspace secret is actually
missing/empty, and immediately return any non-missing error from
s.secrets.Reveal for SecretKeyForWorkspace. Keep the fallback behavior limited
to a true not-found case, and preserve the existing nil-secrets handling.
apps/backend/internal/sentry/handlers.go (1)

159-161: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for workspace-membership/ownership middleware wired around the Sentry routes.
fd -a 'controller.go' apps/backend/internal/sentry
rg -n 'RegisterHTTPRoutes|Group\(' apps/backend/internal/sentry/controller.go apps/backend/internal/sentry/handlers.go
rg -ni 'RequireWorkspace|workspaceAccess|WorkspaceMembership|workspace_id' apps/backend --iglob '*.go' -g '!*_test.go' | rg -i 'sentry|middleware|router'

Repository: kdlbs/kandev

Length of output: 4069


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect Sentry route wiring and workspace-scoping helpers.
sed -n '1,140p' apps/backend/internal/sentry/handlers.go
printf '\n---\n'
sed -n '1,260p' apps/backend/internal/sentry/handlers_issue_watch.go
printf '\n---\n'
sed -n '1,220p' apps/backend/internal/sentry/handlers_config.go 2>/dev/null || true
printf '\n---\n'
sed -n '1,220p' apps/backend/internal/sentry/handlers_issue.go 2>/dev/null || true

Repository: kdlbs/kandev

Length of output: 10684


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether Sentry routes are wrapped by workspace-membership middleware or auth gates elsewhere.
rg -n 'RegisterRoutes\(|RegisterHTTPRoutes\(|Use\(|middleware|auth|workspace|membership|ownership|RequireWorkspace|RequireAuth' apps/backend/internal apps/backend/cmd apps/backend -g '!**/*_test.go'

Repository: kdlbs/kandev

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the Sentry service methods to see whether they validate workspace access internally.
sed -n '1,260p' apps/backend/internal/sentry/service.go
printf '\n---\n'
sed -n '260,560p' apps/backend/internal/sentry/service.go

Repository: kdlbs/kandev

Length of output: 18525


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the top-level backend route setup for any router-level middleware before integration routes are registered.
fd -a 'main.go|server.go|router.go|routes.go' apps/backend

Repository: kdlbs/kandev

Length of output: 943


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect Sentry service methods for any workspace membership checks or caller authorization.
sed -n '1,260p' apps/backend/internal/sentry/service.go
printf '\n---\n'
sed -n '260,620p' apps/backend/internal/sentry/service.go

Repository: kdlbs/kandev

Length of output: 20463


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where backend routers are initialized and whether any auth/membership middleware is applied globally.
fd -a 'main.go|server.go|router.go|routes.go' apps/backend
printf '\n---\n'
rg -n 'Use\(|Middleware|auth|session|workspace|membership|ownership|RegisterRoutes\(' apps/backend -g '!**/*_test.go' | sed -n '1,220p'

Repository: kdlbs/kandev

Length of output: 24025


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect top-level backend route setup to see what middleware protects integration routes.
git ls-files 'apps/backend/**/*.go' | rg -n 'main.go|server.go|router.go|routes.go|http|router|route'
printf '\n---\n'
rg -n 'gin\.New|Use\(|RegisterRoutes\(|RegisterHTTPRoutes\(' apps/backend -g '!**/*_test.go'

Repository: kdlbs/kandev

Length of output: 16585


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the backend router entrypoint and inspect the surrounding setup.
fd -a 'main.go|server.go|router.go|routes.go' apps/backend

Repository: kdlbs/kandev

Length of output: 943


Add a workspace access check before using workspace_id
The Sentry routes are mounted directly on the shared backend router, which only adds logging/tracing/recovery/CORS. Add a workspace-membership check before using workspace_id; otherwise callers can switch workspaces by changing the query param.

🤖 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 `@apps/backend/internal/sentry/handlers.go` around lines 159 - 161, The
workspaceID helper currently trusts the raw workspace_id query param, which
allows selecting arbitrary workspaces. Add a workspace-membership authorization
check in the Sentry request flow before any handler uses workspaceID from the
Controller, and reject requests when the caller is not a member of that
workspace. Use the existing workspaceID(ctx) entry point and the surrounding
Sentry handler/controller methods to locate where to enforce the check so the
query param cannot be used to switch workspaces.
apps/web/app/jira/jira-page-client.tsx (1)

171-198: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## jira-page-client outline\n'
ast-grep outline apps/web/app/jira/jira-page-client.tsx --view expanded || true

printf '\n## jira-page-client relevant ranges\n'
sed -n '150,290p' apps/web/app/jira/jira-page-client.tsx
printf '\n---\n'
sed -n '290,380p' apps/web/app/jira/jira-page-client.tsx

printf '\n## jira-settings outline\n'
ast-grep outline apps/web/components/jira/jira-settings.tsx --view expanded || true

printf '\n## jira-settings relevant ranges\n'
sed -n '1,220p' apps/web/components/jira/jira-settings.tsx

Repository: kdlbs/kandev

Length of output: 18557


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## JiraPageClient usages\n'
rg -n "JiraPageClient" apps/web -g '!**/node_modules/**' || true

printf '\n## workspaceId prop flow around Jira page route\n'
rg -n "workspaceId" apps/web/app/jira apps/web/components/jira -g '!**/node_modules/**' || true

printf '\n## route/page files for jira\n'
git ls-files 'apps/web/app/jira/**' 'apps/web/components/jira/**' | sed -n '1,120p'

Repository: kdlbs/kandev

Length of output: 13969


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## apps/web/app/jira/page.tsx\n'
sed -n '1,120p' apps/web/app/jira/page.tsx

printf '\n## apps/web/src/spa-routes.tsx around jira route\n'
sed -n '330,380p' apps/web/src/spa-routes.tsx

Repository: kdlbs/kandev

Length of output: 4161


Remount AuthenticatedView on workspace change. Add key={workspaceId} so useFilterState(defaultProjectKey) re-initializes when the active workspace changes; otherwise the previous workspace’s default project can persist in filters.projectKeys and show a stale Jira filter.

🤖 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 `@apps/web/app/jira/jira-page-client.tsx` around lines 171 - 198,
AuthenticatedView is preserving filter state across workspace switches because
useFilterState(defaultProjectKey) is not re-initialized when workspaceId
changes. Add a key based on workspaceId to the AuthenticatedView component so it
remounts on workspace change, ensuring filters.projectKeys resets to the correct
defaultProjectKey for the active workspace and does not carry over stale Jira
filters.

@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

Claude finished @carlosflorencio's task in 2m 38s —— View job


Code Review

Todo

  • Load tools and skill
  • Read SKILL.md for review guidelines
  • Check what new commits have been pushed since last review
  • Review changed files
  • Post inline findings and summary

Reviewed one new commit since last round: 03c6b25 (fix: scope integration e2e helpers).

What this round verified

  • Mock auth-health handlers workspace-scoped — Jira, Linear, and Sentry mock_controller.go now route PUT /mock/auth-health?workspace_id=... through UpdateAuthHealthForWorkspace while falling back to the global variant when workspace_id is absent. Pattern is consistent across all three.
  • useProjectStatuses workspace-scoped — Hook accepts workspaceId?: string | null, namespaces the per-project cache with workspaceKey\0projectKey, and passes workspaceId to listJiraProjectStatuses. The cacheKey dependency includes the workspace so the effect re-runs on workspace switch; setLoaded(false) is called at the top of the effect so consumers never see stale data marked as loaded during a switch. Correct.
  • jira-page-client.tsxworkspaceId is now threaded into useProjectStatuses, closing the previous gap where project statuses were fetched without workspace context.
  • E2E cleanup hardenedintegrationCleanup now deletes workspace-scoped Jira/Linear/Sentry configs before deleting global ones, and mockSentryReset() is included in the mock reset batch. Sentry was previously missing from cleanup entirely; this is the correct fix.
  • API client helperswithActiveWorkspace() and activeWorkspaceId() provide a consistent pattern for auto-appending workspace context; setJiraConfig, setLinearConfig, waitForIntegrationAuthHealthy, and all three mock*SetAuthHealth helpers are updated with optional workspaceId. Signature change for waitForIntegrationAuthHealthy (from timeoutMs: number to options: number | { timeoutMs?, workspaceId? }) is backward-compatible.

Findings

No blockers. No suggestions.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge. This commit scopes the remaining E2E test infrastructure (mock auth-health, project statuses, cleanup, API helpers) to workspaces, completing the test coverage layer for all four integrations.

@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

🧹 Nitpick comments (1)
apps/web/e2e/fixtures/test-base.ts (1)

196-212: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Batch the six cleanup DELETEs to cut per-test overhead.

The DELETE calls are independent and already individually swallow errors — running them in parallel via Promise.all (alongside the existing mock-reset batch) would reduce latency added to every test by this auto: true fixture.

⚡ Suggested batching
     async ({ apiClient, seedData }, use) => {
       const scoped = `workspace_id=${encodeURIComponent(seedData.workspaceId)}`;
-      await apiClient.rawRequest("DELETE", `/api/v1/jira/config?${scoped}`).catch(() => undefined);
-      await apiClient
-        .rawRequest("DELETE", `/api/v1/linear/config?${scoped}`)
-        .catch(() => undefined);
-      await apiClient
-        .rawRequest("DELETE", `/api/v1/sentry/config?${scoped}`)
-        .catch(() => undefined);
-      await apiClient.rawRequest("DELETE", `/api/v1/jira/config`).catch(() => undefined);
-      await apiClient.rawRequest("DELETE", `/api/v1/linear/config`).catch(() => undefined);
-      await apiClient.rawRequest("DELETE", `/api/v1/sentry/config`).catch(() => undefined);
-      await Promise.all([
+      await Promise.all([
+        apiClient.rawRequest("DELETE", `/api/v1/jira/config?${scoped}`).catch(() => undefined),
+        apiClient.rawRequest("DELETE", `/api/v1/linear/config?${scoped}`).catch(() => undefined),
+        apiClient.rawRequest("DELETE", `/api/v1/sentry/config?${scoped}`).catch(() => undefined),
+        apiClient.rawRequest("DELETE", `/api/v1/jira/config`).catch(() => undefined),
+        apiClient.rawRequest("DELETE", `/api/v1/linear/config`).catch(() => undefined),
+        apiClient.rawRequest("DELETE", `/api/v1/sentry/config`).catch(() => undefined),
         apiClient.mockJiraReset().catch(() => undefined),
         apiClient.mockLinearReset().catch(() => undefined),
         apiClient.mockSentryReset().catch(() => undefined),
       ]);

Based on learnings, teardown here is intentionally best-effort with swallowed failures, which this refactor preserves.

🤖 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 `@apps/web/e2e/fixtures/test-base.ts` around lines 196 - 212, The teardown in
the auto fixture currently runs six independent DELETE requests sequentially in
the cleanup block, adding unnecessary per-test latency. Refactor the cleanup
around the scoped `workspace_id` and the three unscoped `/api/v1/*/config`
deletes to execute in a single `Promise.all`, similar to the existing
`mockJiraReset`/`mockLinearReset`/`mockSentryReset` batch, while keeping the
current best-effort `.catch(() => undefined)` behavior on each request.

Source: Learnings

🤖 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 `@apps/web/components/jira/my-jira/use-project-statuses.test.ts`:
- Around line 5-8: The mock in use-project-statuses.test.ts is using the old
vi.fn<[...], ...>() generic form, which is not supported in Vitest 1.6. Update
listJiraProjectStatusesMock to use the single function-signature generic style
accepted by Vitest 1.6 so the typecheck stays green, and keep the mock’s
parameter and promise return types aligned with the existing
listJiraProjectStatuses usage.

---

Nitpick comments:
In `@apps/web/e2e/fixtures/test-base.ts`:
- Around line 196-212: The teardown in the auto fixture currently runs six
independent DELETE requests sequentially in the cleanup block, adding
unnecessary per-test latency. Refactor the cleanup around the scoped
`workspace_id` and the three unscoped `/api/v1/*/config` deletes to execute in a
single `Promise.all`, similar to the existing
`mockJiraReset`/`mockLinearReset`/`mockSentryReset` batch, while keeping the
current best-effort `.catch(() => undefined)` behavior on each request.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2cc0e7d3-46e9-4fc9-a07c-6da02281fd36

📥 Commits

Reviewing files that changed from the base of the PR and between bf51332 and 03c6b25.

📒 Files selected for processing (8)
  • apps/backend/internal/jira/mock_controller.go
  • apps/backend/internal/linear/mock_controller.go
  • apps/backend/internal/sentry/mock_controller.go
  • apps/web/app/jira/jira-page-client.tsx
  • apps/web/components/jira/my-jira/use-project-statuses.test.ts
  • apps/web/components/jira/my-jira/use-project-statuses.ts
  • apps/web/e2e/fixtures/test-base.ts
  • apps/web/e2e/helpers/api-client.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/jira/jira-page-client.tsx

Comment thread apps/web/components/jira/my-jira/use-project-statuses.test.ts
@carlosflorencio carlosflorencio merged commit c3f713e into main Jul 6, 2026
65 checks passed
@carlosflorencio carlosflorencio deleted the feature/integrations-per-wor-1rk branch July 6, 2026 20:43
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.

[Feature]: Bind a GitHub repository to a workspace to limit GitHub integration scope

1 participant