Skip to content

fix: gracefully handle deleted GitHub repos in vault detail page - #6

Merged
NicolasRitouet merged 2 commits into
mainfrom
fix/graceful-deleted-repo-handling
Mar 25, 2026
Merged

fix: gracefully handle deleted GitHub repos in vault detail page#6
NicolasRitouet merged 2 commits into
mainfrom
fix/graceful-deleted-repo-handling

Conversation

@NicolasRitouet

@NicolasRitouet NicolasRitouet commented Mar 25, 2026

Copy link
Copy Markdown
Member

Summary

  • When a GitHub repo backing a vault is deleted, the dashboard was completely blocked with "GitHub App Not Installed" error on the vault detail page
  • Backend: getVaultByRepo now catches specific ForbiddenError (not all errors) and returns a warning: "repo_inaccessible" field, with ownership check so only vault owners see the degraded view
  • Backend: new requireAdminOrOwnerAccess middleware (scoped to DELETE route only) falls back to DB ownership check when GitHub API is unavailable
  • Frontend: shows warning banner with delete button, hides permission badge and syncs, disables write operations for inaccessible repos

Test plan

  • Delete a GitHub repo that has a Keyway vault
  • Verify vault list page still shows the vault with "Repo not found" warning (pre-existing behavior)
  • Verify vault detail page shows warning banner instead of error page
  • Verify secrets are visible in read-only mode (no edit/create/delete)
  • Verify "Delete vault" button works from the warning banner
  • Verify a non-owner cannot access the vault detail page of a deleted repo
  • Verify transient GitHub API errors (non-ForbiddenError) still propagate correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Vault owners can now delete vaults (previously admin-only action).
  • Bug Fixes

    • Improved handling when GitHub App access is unavailable—vault owners can still view and delete their vaults with a warning indicator.
    • Repo-inaccessible warning disables write operations and hides integration details until access is restored.

When a GitHub repo backing a vault is deleted, the dashboard was
completely blocked with "GitHub App Not Installed" error. This fix
shows a degraded read-only view with a warning banner and delete option.

- Backend: catch specific ForbiddenError in getVaultByRepo, add
  ownership check so only vault owners see inaccessible vaults
- Backend: add requireAdminOrOwnerAccess middleware for DELETE route,
  falling back to DB ownership when GitHub API is unavailable
- Frontend: show warning banner with delete button, hide permission
  badge and syncs, disable write operations for inaccessible repos

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 25, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
keyway-dashboard Ready Ready Preview, Comment Mar 25, 2026 4:12am

Request Review

@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@NicolasRitouet has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 50 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae450d00-99f2-4ec7-a668-9f5c98228f3b

📥 Commits

Reviewing files that changed from the base of the PR and between 84238a0 and f6b9611.

📒 Files selected for processing (2)
  • packages/backend/tests/integration/environments.test.ts
  • packages/dashboard/tests/pages/VaultDetailPage.test.tsx
📝 Walkthrough

Walkthrough

This PR enables vault owners to delete their vaults even when the GitHub App isn't installed by introducing a fallback authorization path. When GitHub App access fails, the system checks vault ownership directly in the database. A new "repo_inaccessible" warning marks vaults where repo access couldn't be verified, with corresponding UI handling to restrict operations and provide deletion capability.

Changes

Cohort / File(s) Summary
Route & Middleware Authorization
packages/backend/src/api/v1/routes/vaults.routes.ts, packages/backend/src/middleware/auth.ts
Updated DELETE vault route to use new requireAdminOrOwnerAccess middleware. Added middleware that checks admin status via GitHub App, and on "GitHub App not installed" error, falls back to verifying the user is the vault owner in the database.
Vault Service Logic
packages/backend/src/services/vault.service.ts
Extended VaultDetails interface with optional warning?: "repo_inaccessible". Modified getVaultByRepo to wrap GitHub App role check in try/catch, setting warning and allowing owners to access vaults when repo role lookup fails, while denying access to non-owners.
Frontend UI & API Client
packages/dashboard/app/(dashboard)/vaults/[owner]/[repo]/page.tsx, packages/dashboard/lib/api/vaults.ts
Added repo-inaccessible UI handling: conditionally disables writes, hides permission badges and integrations section, displays amber warning with delete vault action. Updated API client to conditionally include warning in response mapping.

Sequence Diagram

sequenceDiagram
    actor User
    participant Client
    participant Route Handler
    participant Auth Middleware
    participant Vault Service
    participant GitHub App
    participant Database
    
    User->>Client: DELETE /vaults/:owner/:repo
    Client->>Route Handler: Send delete request
    Route Handler->>Auth Middleware: Check authorization
    Auth Middleware->>Vault Service: Get user role with GitHub App
    Vault Service->>GitHub App: Query user role
    alt GitHub App installed
        GitHub App-->>Vault Service: Return user role
        Vault Service-->>Auth Middleware: Return role
        alt User is admin
            Auth Middleware-->>Route Handler: Authorization granted
            Route Handler->>Database: Delete vault
            Database-->>Route Handler: Success
            Route Handler-->>Client: Vault deleted
        else User not admin
            Auth Middleware-->>Route Handler: ForbiddenError
            Route Handler-->>Client: Access denied
        end
    else GitHub App not installed
        GitHub App-->>Vault Service: ForbiddenError (not installed)
        Vault Service->>Database: Query vault owner
        Database-->>Vault Service: Return vault details
        alt Authenticated user is vault owner
            Vault Service-->>Auth Middleware: Authorization granted
            Auth Middleware-->>Route Handler: Authorization granted
            Route Handler->>Database: Delete vault
            Database-->>Route Handler: Success
            Route Handler-->>Client: Vault deleted
        else User not vault owner
            Vault Service-->>Auth Middleware: ForbiddenError
            Auth Middleware-->>Route Handler: ForbiddenError
            Route Handler-->>Client: Access denied
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A vault owner's plight, no GitHub App in sight,
Yet the fallback path shines, checking database lines,
When the app goes away, ownership saves the day,
Trash icon gleams bright, empowering deletion's right! 🗑️✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: gracefully handling deleted GitHub repos in vault detail page, which aligns with all the changes across backend and frontend components.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/graceful-deleted-repo-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/dashboard/app/(dashboard)/vaults/[owner]/[repo]/page.tsx (1)

478-487: Consider adding loading state during vault deletion.

The handleDeleteVault function doesn't set any loading state, which could allow users to click "Delete vault" multiple times. Consider reusing isSubmitting or adding a dedicated state.

Suggested improvement
   const handleDeleteVault = async () => {
+    setIsSubmitting(true)
     try {
       await api.deleteVault(owner, repo)
       setShowDeleteModal(false)
       queryClient.invalidateQueries({ queryKey: ['vaults'] })
       router.push('/')
     } catch (err) {
       toast.error(err instanceof Error ? err.message : 'Failed to delete vault')
+    } finally {
+      setIsSubmitting(false)
     }
   }

Then pass isSubmitting to DeleteVaultModal to disable the confirm button during the operation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/dashboard/app/`(dashboard)/vaults/[owner]/[repo]/page.tsx around
lines 478 - 487, handleDeleteVault lacks a loading state so users can click
delete multiple times; add a boolean state (e.g. const [isDeleting,
setIsDeleting] = useState(false)) or reuse existing isSubmitting, set
setIsDeleting(true) before the await and setIsDeleting(false) in a finally
block, keep the existing api.deleteVault, queryClient.invalidateQueries and
router.push calls inside try, and pass isDeleting to the DeleteVaultModal as a
prop to disable the confirm button while the deletion is in progress.
packages/dashboard/lib/api/vaults.ts (1)

44-44: Minor inconsistency in warning field mapping.

getVaultByRepo (line 102) uses conditional spread to only include warning when truthy, but getVaults (line 44) always assigns warning via type assertion. Consider aligning both for consistency:

Suggested fix for getVaults
       permission: v.permission as Vault['permission'],
-      warning: v.warning as Vault['warning'],
+      ...(v.warning && { warning: v.warning as Vault['warning'] }),
       is_private: v.isPrivate,

Also applies to: 102-102

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/dashboard/lib/api/vaults.ts` at line 44, getVaults currently assigns
the warning field unconditionally using "warning: v.warning as
Vault['warning']", which is inconsistent with getVaultByRepo that only includes
warning when truthy; update getVaults to mirror getVaultByRepo by conditionally
spreading the warning field (e.g., include ...(v.warning ? { warning: v.warning
} : {}) ) or otherwise only set warning when v.warning is truthy so both
functions map the Vault['warning'] consistently; modify the mapping where "v" is
transformed into a Vault object in getVaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/backend/src/middleware/auth.ts`:
- Around line 361-399: Tests are failing because the test mocks for the auth
middleware do not export the newly added function requireAdminOrOwnerAccess;
update the mock for the "../../src/middleware/auth" module to include
requireAdminOrOwnerAccess (e.g., extend the mocked export with
requireAdminOrOwnerAccess: vi.fn().mockImplementation(async () => {})) so tests
see and can stub this new async function; ensure the mock returns the original
module spread (...actual) plus the new mocked symbol requireAdminOrOwnerAccess.

In `@packages/dashboard/app/`(dashboard)/vaults/[owner]/[repo]/page.tsx:
- Line 470: The test mocks are missing an export for useRouter from
next/navigation; update the mock (where you call vi.mock for "next/navigation")
to import the real module via the async importOriginal pattern and return an
object that spreads the actual exports and adds useRouter as a vi.fn that
returns an object with push, replace, and back mock functions so components
using useRouter (e.g., the useRouter() call) have the expected methods during
tests.

---

Nitpick comments:
In `@packages/dashboard/app/`(dashboard)/vaults/[owner]/[repo]/page.tsx:
- Around line 478-487: handleDeleteVault lacks a loading state so users can
click delete multiple times; add a boolean state (e.g. const [isDeleting,
setIsDeleting] = useState(false)) or reuse existing isSubmitting, set
setIsDeleting(true) before the await and setIsDeleting(false) in a finally
block, keep the existing api.deleteVault, queryClient.invalidateQueries and
router.push calls inside try, and pass isDeleting to the DeleteVaultModal as a
prop to disable the confirm button while the deletion is in progress.

In `@packages/dashboard/lib/api/vaults.ts`:
- Line 44: getVaults currently assigns the warning field unconditionally using
"warning: v.warning as Vault['warning']", which is inconsistent with
getVaultByRepo that only includes warning when truthy; update getVaults to
mirror getVaultByRepo by conditionally spreading the warning field (e.g.,
include ...(v.warning ? { warning: v.warning } : {}) ) or otherwise only set
warning when v.warning is truthy so both functions map the Vault['warning']
consistently; modify the mapping where "v" is transformed into a Vault object in
getVaults.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1d2d2ffa-09db-4c9a-b770-40a6d8cb077f

📥 Commits

Reviewing files that changed from the base of the PR and between 63cb2d8 and 84238a0.

📒 Files selected for processing (5)
  • packages/backend/src/api/v1/routes/vaults.routes.ts
  • packages/backend/src/middleware/auth.ts
  • packages/backend/src/services/vault.service.ts
  • packages/dashboard/app/(dashboard)/vaults/[owner]/[repo]/page.tsx
  • packages/dashboard/lib/api/vaults.ts

Comment thread packages/backend/src/middleware/auth.ts
}
}

const router = useRouter()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pipeline failure: Test mocks need to export useRouter.

The CI is failing because the test mock for next/navigation doesn't include useRouter. Update the test mocks to include this export.

Example fix for test mocks
vi.mock(import("next/navigation"), async (importOriginal) => {
  const actual = await importOriginal()
  return {
    ...actual,
    useRouter: vi.fn(() => ({
      push: vi.fn(),
      replace: vi.fn(),
      back: vi.fn(),
    })),
  }
})
🧰 Tools
🪛 GitHub Actions: CI: Dashboard

[error] 470-470: Vitest failed due to missing mock export: "No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?"

🪛 GitHub Check: build

[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Secrets List > should render all secrets
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7


[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Environment Filters > should filter secrets when environment is clicked
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7


[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Environment Filters > should show All filter by default
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7


[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Environment Filters > should render all environments as filter buttons
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7


[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Vault Header > should show Collaborators button for admin
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7


[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Vault Header > should show permission badge
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7


[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Vault Header > should show Add Secrets dropdown for admin users
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7


[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Vault Header > should render vault avatar
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7


[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Vault Header > should render vault name and stats
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7


[failure] 470-470: tests/pages/VaultDetailPage.test.tsx > VaultDetailPage > Loading State > should show loading skeletons when data is loading
Error: [vitest] No "useRouter" export is defined on the "next/navigation" mock. Did you forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:

vi.mock(import("next/navigation"), async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
// your mocked methods
}
})

❯ VaultDetailPage app/(dashboard)/vaults/[owner]/[repo]/page.tsx:470:18
❯ renderWithHooks ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:15486:18
❯ mountIndeterminateComponent ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:20103:13
❯ beginWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:21626:16
❯ beginWork$1 ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:27465:14
❯ performUnitOfWork ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26599:12
❯ workLoopSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26505:5
❯ renderRootSync ../../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.development.js:26473:7

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/dashboard/app/`(dashboard)/vaults/[owner]/[repo]/page.tsx at line
470, The test mocks are missing an export for useRouter from next/navigation;
update the mock (where you call vi.mock for "next/navigation") to import the
real module via the async importOriginal pattern and return an object that
spreads the actual exports and adds useRouter as a vi.fn that returns an object
with push, replace, and back mock functions so components using useRouter (e.g.,
the useRouter() call) have the expected methods during tests.

- Backend: add requireAdminOrOwnerAccess mock in environments.test.ts
- Dashboard: add useRouter mock in VaultDetailPage.test.tsx
- Dashboard: add DeleteVaultModal mock in VaultDetailPage.test.tsx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@NicolasRitouet
NicolasRitouet merged commit 5441046 into main Mar 25, 2026
5 checks passed
NicolasRitouet added a commit that referenced this pull request Jun 16, 2026
Adds getOrgMembership to the github mock + a test asserting the
installation-token role overrides the user-token role (fix #6 was only
exercised via the catch-fallback before). Also documents why
ensureOrganizationExists is intentionally insert-only (no role rewrite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant