Skip to content

fix(dev): isolate Supabase per git worktree#1619

Open
riderx wants to merge 16 commits intomainfrom
riderx/supabase-worktree
Open

fix(dev): isolate Supabase per git worktree#1619
riderx wants to merge 16 commits intomainfrom
riderx/supabase-worktree

Conversation

@riderx
Copy link
Member

@riderx riderx commented Feb 10, 2026

Summary (AI generated)

  • Add a worktree-aware Supabase wrapper that generates a per-worktree project_id and port set under .context/ to avoid Docker/port collisions.
  • Route bun backend, bun reset, and bun test:* through the wrapper so each worktree uses its own local Supabase instance.

Test plan (AI generated)

  • bun run supabase:start
  • bun run supabase:status (confirm ports differ across worktrees)
  • bun run test:all

Screenshots (AI generated)

N/A

Checklist (AI generated)

  • My code follows the code style of this project and passes
    bun run lint:backend && bun run lint.
  • My change requires a change to the documentation.
  • I have updated the documentation
    accordingly.
  • My change has adequate E2E test coverage.
  • I have tested my code manually, and I have provided steps how to reproduce
    my tests

Generated with AI

Summary by CodeRabbit

  • Chores

    • Switched local Supabase commands across docs and scripts to Bun-based variants and added package scripts; bumped release version.
  • New Features

    • Worktree-isolated local Supabase orchestration and deterministic per-worktree port configuration.
    • New script to update Cloud SQL authorized networks.
  • Bug Fixes / Improvements

    • More robust URL/host/port handling and local environment env propagation.
    • Restored DB permission for service_role top-ups.
  • UI / i18n

    • Added credits-only informational banner and translations across languages.
  • Tests

    • Increased test setup timeouts for stability.

Copilot AI review requested due to automatic review settings February 10, 2026 22:44
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces direct Supabase CLI calls with Bun-run package scripts, adds per-worktree Supabase configuration and orchestration (deterministic ports per worktree), adjusts URL rewriting to honor forwarded headers, updates scripts/package.json/tests/docs, and adds a migration granting EXECUTE on top_up_usage_credits. (50 words)

Changes

Cohort / File(s) Summary
Docs & Guides
AGENTS.md, CONTRIBUTING.md, README.md, supabase/migration_guide.md, .github/copilot-instructions.md
Replaced supabase CLI invocations with bun run supabase:* equivalents and updated guidance to mention worktree-specific/dynamic URLs and Bun-based frontend commands.
Package scripts & test wrappers
package.json
Bumped version; added supabase:* and supabase:with-env scripts; updated env:hard-setup, backend, reset, and many test:* scripts to run via Bun-run supabase:with-env. Review test invocation changes.
Worktree config & orchestration
scripts/supabase-worktree-config.ts, scripts/supabase-worktree.ts
New modules for deterministic per-worktree ports/config, per-worktree supabase dir/config rewrite, status parsing, with-env helpers, and a CLI entrypoint; inspect FS/symlink ops and config.toml rewriting.
Runtime env discovery script
scripts/start-cloudflare-workers.sh
Replaced direct CLI selection with run_supabase_status_env that tries bun run supabase:status, supabase status, then bunx supabase status and populates SUPA_ENV from status output.
Playwright / test infra
playwright.config.ts, tests/test-utils.ts, tests/device.test.ts, tests/events.test.ts
Playwright uses worktree config for dynamic Supabase API port; tests added normalizePostgresUrl and expanded service key lookup; beforeAll timeouts increased to 60s.
Backend URL handling & edge runtime fixes
supabase/functions/_backend/files/files.ts, .../supabaseTusProxy.ts, .../utils/downloadUrl.ts, .../utils/stripe.ts
URL/location rewriting now derives base from X-Forwarded-* headers, improved host/port resolution, and new local edge-runtime URL rewriting utilities; Supabase Studio port computed from SUPABASE_URL.
Database & migrations
supabase/seed.sql, supabase/migrations/...restore_top_up_usage_credits_for_service_role.sql
Seed DB URL changed to http://kong:8000 with comments; added migration granting EXECUTE on top_up_usage_credits to service_role.
Credits & billing UI / i18n
src/components/CreditsCta.vue, src/pages/settings/organization/Plans.vue, messages/*.json
Added creditsOnly prop and credits-only banner CTA, adjusted plan highlight logic for credits-only orgs, and added new translation keys across locales.
Organization store & credits backend
src/stores/organization.ts, supabase/functions/_backend/private/credits.ts, supabase/functions/_backend/plugins/stats.ts, supabase/functions/_backend/utils/cloudflare.ts
Consider can_use_more when computing org failure; switched to admin client for top_up_usage_credits RPC and added migration; simplified stats column access; changed custom_id selection to argMax.
Other backend triggers & utils
supabase/functions/_backend/triggers/on_version_update.ts, supabase/functions/_backend/utils/version.ts, supabase/functions/_backend/files/...
Ensure owner_org resolution before metadata upserts and manifest handling; bumped exported version to 12.110.6; miscellaneous header/URL handling improvements in file-serving code.
New tooling scripts
scripts/update_cloudsql_authorized_networks.sh
Added script to fetch Cloudflare IPv4 CIDRs and patch Cloud SQL authorized networks with interactive/project/instance selection and dry-run support.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant CLI as "bun run (package scripts)"
    participant WT as "supabase-worktree.ts"
    participant Config as "supabase-worktree-config.ts"
    participant SB as "Supabase CLI/Daemon"
    participant Cmd as "Target Command"

    Dev->>CLI: bun run supabase:with-env <command...>
    CLI->>WT: invoke "with-env" subcommand
    WT->>Config: getSupabaseWorktreeConfig(cwd)
    Config-->>WT: { repoRoot, worktreeHash, projectId, ports }
    WT->>WT: prepare per-worktree supabase dir & rewrite config.toml
    WT->>SB: runSupabase('status') in worktree dir
    SB-->>WT: status output (text/JSON)
    WT->>WT: parse status -> derive env (SUPABASE_URL, KEYS, DB_URL...)
    WT->>Cmd: exec <command...> with derived env
    Cmd-->>Dev: output / result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 Bun-run hops, worktrees bloom and play,
Ports by hash arrange each localhost way,
Headers whisper paths where requests should bend,
Scripts wrap env and tests start up again,
A rabbit cheers: "Run with env — away!" 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (28 files):

⚔️ .github/copilot-instructions.md (content)
⚔️ AGENTS.md (content)
⚔️ CONTRIBUTING.md (content)
⚔️ README.md (content)
⚔️ messages/es.json (content)
⚔️ messages/pt-br.json (content)
⚔️ package.json (content)
⚔️ playwright.config.ts (content)
⚔️ scripts/start-cloudflare-workers.sh (content)
⚔️ scripts/update_cloudsql_authorized_networks.sh (content)
⚔️ src/components/CreditsCta.vue (content)
⚔️ src/pages/admin/dashboard/users.vue (content)
⚔️ src/types/supabase.types.ts (content)
⚔️ supabase/functions/_backend/files/files.ts (content)
⚔️ supabase/functions/_backend/files/supabaseTusProxy.ts (content)
⚔️ supabase/functions/_backend/triggers/logsnag_insights.ts (content)
⚔️ supabase/functions/_backend/triggers/on_version_update.ts (content)
⚔️ supabase/functions/_backend/utils/downloadUrl.ts (content)
⚔️ supabase/functions/_backend/utils/pg.ts (content)
⚔️ supabase/functions/_backend/utils/stripe.ts (content)
⚔️ supabase/functions/_backend/utils/supabase.types.ts (content)
⚔️ supabase/functions/_backend/utils/version.ts (content)
⚔️ supabase/migration_guide.md (content)
⚔️ supabase/migrations/20260214054927_restore_top_up_usage_credits_for_service_role.sql (content)
⚔️ supabase/seed.sql (content)
⚔️ tests/device.test.ts (content)
⚔️ tests/events.test.ts (content)
⚔️ tests/test-utils.ts (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix(dev): isolate Supabase per git worktree' accurately and specifically describes the main change: implementing worktree-isolated Supabase configuration to prevent collisions.
Description check ✅ Passed The PR description follows the required template structure with Summary, Test plan, Screenshots, and Checklist sections. All critical sections are completed with relevant detail about the worktree-aware wrapper and testing steps.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/supabase-worktree

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
README.md (1)

383-385: ⚠️ Potential issue | 🟡 Minor

Inconsistent command: supabase db reset not updated in the "Supabase DB Reset" section.

Line 384 still references the raw supabase db reset while the earlier "Seed Supabase DB locally" section (line 349) was updated to bun run supabase:db:reset. This section should be updated for consistency.

Proposed fix
 ```bash
-supabase db reset
+bun run supabase:db:reset
</details>

</blockquote></details>
<details>
<summary>supabase/migration_guide.md (1)</summary><blockquote>

`64-68`: _⚠️ Potential issue_ | _🟡 Minor_

**Step 7 still references the old `supabase db reset` command.**

For consistency with the rest of this PR (and Step 4 above), this should also be updated to the new worktree-aware command.


<details>
<summary>📝 Proposed fix</summary>

```diff
-supabase db reset
+bun run supabase:db:reset

Based on learnings: "Backend tests modify local database; always reset database with supabase db reset before running tests to ensure clean state" — the guide should reflect the new command so developers follow the correct workflow.

🤖 Fix all issues with AI agents
In `@playwright.config.ts`:
- Line 14: Call getSupabaseWorktreeConfig without passing process.cwd() so it
uses its internal default and avoids referencing the global process; change the
expression const { ports: supabasePorts } =
getSupabaseWorktreeConfig(process.cwd()) to const { ports: supabasePorts } =
getSupabaseWorktreeConfig() and remove any now-unnecessary direct use of
process.cwd() in this statement.

In `@supabase/functions/_backend/files/files.ts`:
- Around line 32-37: getExternalBaseUrl currently uses the raw X-Forwarded-Proto
header which can be a comma-separated list when multiple proxies are involved;
update the logic that computes proto (from protoHeader) to split on ',' and use
the first non-empty trimmed value (e.g., protoHeader.split(',')[0].trim()), then
fall back to new URL(c.req.url).protocol.replace(':','') or 'http' as before;
keep references to protoHeader and proto so you modify the existing assignment
for proto inside getExternalBaseUrl without changing other behavior.

In `@supabase/functions/_backend/utils/stripe.ts`:
- Around line 31-41: The call to Number.parseInt in the isLocalSupabase branch
should include an explicit radix to avoid ambiguity; update the parsing of
api.port in the function handling supabaseUrl (the block using
isLocalSupabase(c), supabaseUrl and customerId) to call Number.parseInt(api.port
|| '54321', 10) so the API port is parsed as a decimal before computing
studioPort.
🧹 Nitpick comments (4)
supabase/functions/_backend/utils/downloadUrl.ts (1)

41-55: Duplicated edge-runtime URL rewriting — extract a shared helper.

The forwarded-header URL fixup logic at lines 41–55 and 68–80 is identical. Consider extracting it to reduce duplication, similar to the getExternalBaseUrl helper in files.ts.

Suggested helper
function resolveEdgeRuntimeUrl(c: Context, url: URL): string {
  if (url.host.endsWith(':8081') && url.hostname.startsWith('supabase_edge_runtime_')) {
    const forwardedHost = c.req.header('X-Forwarded-Host') || c.req.header('Host')
    const forwardedProto = c.req.header('X-Forwarded-Proto')?.split(',')[0]?.trim()
    if (forwardedHost) {
      url.host = forwardedHost
      if (forwardedProto)
        url.protocol = `${forwardedProto}:`
    }
    else {
      url.host = 'localhost:54321'
    }
    return `functions/v1/${BASE_PATH}`
  }
  return BASE_PATH
}

Then both call sites become:

const finalPath = resolveEdgeRuntimeUrl(c, url)

Also applies to: 68-80

scripts/supabase-worktree-config.ts (1)

43-43: Port offset design is sound but sensitive to the * 10 factor.

The offset / 10 on Line 68 relies on offset always being a multiple of 10 (guaranteed by the * 10 here). If this factor is ever changed, edgeInspector would get a fractional port number. Consider adding Math.floor() defensively on Line 68, or a comment here noting the coupling.

🔧 Defensive fix for edgeInspector
-      edgeInspector: base.edgeInspector + (offset / 10),
+      edgeInspector: base.edgeInspector + Math.floor(offset / 10),
scripts/supabase-worktree.ts (2)

100-107: parseStatusJson may fail if JSON is followed by trailing text.

JSON.parse rejects trailing non-whitespace characters. If supabase status -o json ever emits text after the JSON object, mixed.slice(idx) will include it and parsing will throw. The catch in getStatusJson handles this gracefully today, but you could make parsing more robust by extracting only the JSON object.

🔧 Trim trailing content after the JSON object
 function parseStatusJson(mixed: string): any {
-  // `supabase status -o json` can print non-JSON lines like:
-  // "Stopped services: [...]"
   const idx = mixed.indexOf('{')
   if (idx < 0)
     throw new Error('Failed to parse supabase status output (no JSON object found).')
-  return JSON.parse(mixed.slice(idx))
+  const candidate = mixed.slice(idx)
+  // Find the matching closing brace to tolerate trailing output
+  let depth = 0
+  for (let i = 0; i < candidate.length; i++) {
+    if (candidate[i] === '{') depth++
+    else if (candidate[i] === '}') depth--
+    if (depth === 0)
+      return JSON.parse(candidate.slice(0, i + 1))
+  }
+  return JSON.parse(candidate)
 }

83-91: Hardcoded symlink list may become stale.

If new entries (e.g., new directories or files) are added to supabase/ in the future, they won't be symlinked automatically. Consider a brief comment noting this needs updating when the supabase directory structure changes, or dynamically listing entries and excluding only config.toml.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a worktree-aware local Supabase wrapper and updates dev/test tooling to avoid port/Docker collisions when using multiple git worktrees.

Changes:

  • Add scripts/supabase-worktree.ts + scripts/supabase-worktree-config.ts to generate per-worktree project_id and port offsets, and to export SUPABASE_* env vars for child commands.
  • Route bun backend, bun reset, and most bun test:* scripts through supabase:with-env so tests automatically target the correct worktree’s Supabase instance.
  • Update various local URL/host assumptions (seed secret, Studio link, file URL rewriting, docs) to better support non-default ports and container/internal hosts.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test-utils.ts Read Postgres URL from SUPABASE_DB_URL/DB_URL and normalize localhost → IPv4
supabase/seed.sql Use http://kong:8000 for DB-side job callback base in local docker network
supabase/migration_guide.md Update docs to use bun run supabase:start
supabase/functions/_backend/utils/stripe.ts Compute Studio port from Supabase API port (+2) for local instances
supabase/functions/_backend/utils/downloadUrl.ts Prefer forwarded headers when building URLs under Edge Runtime container hostnames
supabase/functions/_backend/files/supabaseTusProxy.ts Improve forwarded host/proto handling to preserve worktree ports
supabase/functions/_backend/files/files.ts Replace hardcoded localhost base with forwarded-derived base for public storage URLs
scripts/supabase-worktree.ts New wrapper: symlink supabase dir, rewrite config.toml ports/project_id, run commands with exported env
scripts/supabase-worktree-config.ts New helper: derive stable worktree hash + per-service port offsets
scripts/start-cloudflare-workers.sh Prefer wrapper-based supabase:status when available
playwright.config.ts Use derived Supabase API port for backend webServer port
package.json Add supabase:* scripts and route backend/reset/tests through wrapper
README.md Document new supabase:* commands and status usage
CONTRIBUTING.md Update “running supabase” instruction to wrapper command
AGENTS.md Update local Supabase start/reset instructions to wrapper commands

Comment on lines 184 to 188
const res = spawnSync(cmdArgs[0], cmdArgs.slice(1), {
stdio: 'inherit',
env: childEnv,
shell: false,
})
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

runWithEnv() spawns the passed command with shell: false. On Windows this commonly fails for package-bin shims like cross-env/vitest (which are .cmd files), and can break the updated test:* scripts. Consider enabling shell on win32 or resolving the actual executable path before spawning.

Copilot uses AI. Check for mistakes.
function getExternalBaseUrl(c: Context): string {
const protoHeader = c.req.header('X-Forwarded-Proto')
const proto = protoHeader || new URL(c.req.url).protocol.replace(':', '') || 'http'
const host = c.req.header('X-Forwarded-Host') || c.req.header('Host') || 'localhost:54321'
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

In getExternalBaseUrl(), X-Forwarded-Host is preferred over Host, but X-Forwarded-Host can omit the port (you handled this in supabaseTusProxy). That can generate redirect URLs like http://localhost/... (missing :), breaking local/worktree setups. Consider appending X-Forwarded-Port when X-Forwarded-Host has no port, or falling back to Host when it includes a port.

Suggested change
const host = c.req.header('X-Forwarded-Host') || c.req.header('Host') || 'localhost:54321'
const forwardedHost = c.req.header('X-Forwarded-Host')
const forwardedPort = c.req.header('X-Forwarded-Port')
const hostHeader = c.req.header('Host') || 'localhost:54321'
let host: string
if (forwardedHost) {
// If X-Forwarded-Host already has a port, use it directly.
// A simple check is sufficient here; we only need to avoid the obvious "no-port" case.
const hasPort = forwardedHost.includes(':')
if (hasPort) {
host = forwardedHost
}
else if (forwardedPort) {
host = `${forwardedHost}:${forwardedPort}`
}
else {
// Fallback to Host header (which typically includes the port in local/worktree setups)
host = hostHeader
}
}
else {
host = hostHeader
}

Copilot uses AI. Check for mistakes.
Comment on lines 8 to 10
function which(cmd: string): boolean {
const res = spawnSync('command', ['-v', cmd], { shell: true, stdio: 'ignore' })
return res.status === 0
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

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

which() uses spawnSync('command', ['-v', ...], { shell: true }), relying on the POSIX command builtin. This will fail on non-POSIX shells (notably Windows), making the wrapper unable to detect whether supabase is installed. Consider using a cross-platform check (e.g. process.platform === 'win32' ? where : command -v, or attempting spawnSync(cmd, ['--version']) and checking the exit code).

Copilot uses AI. Check for mistakes.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: d77551b4fa

ℹ️ 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 on lines 44 to 47
const forwardedHost = c.req.header('X-Forwarded-Host') || c.req.header('Host')
const forwardedProto = c.req.header('X-Forwarded-Proto')
if (forwardedHost) {
url.host = forwardedHost

Choose a reason for hiding this comment

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

P2 Badge Preserve localhost fallback when forwarded host is missing

In the supabase_edge_runtime_*:8081 rewrite branch, forwardedHost now falls back to Host, so when X-Forwarded-Host is absent the code can re-use the same internal container host that triggered this branch and emit non-routable download URLs. This is a regression from the previous unconditional localhost fallback and can break local/dev bundle and manifest downloads in setups where the proxy does not forward X-Forwarded-Host.

Useful? React with 👍 / 👎.

@socket-security
Copy link

socket-security bot commented Feb 10, 2026

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
.github/copilot-instructions.md (1)

72-73: ⚠️ Potential issue | 🟡 Minor

Stale port number in documentation.

Line 73 still references :54321 as the backend port, but with the worktree changes the API port is now dynamic (base 54321 + offset). Consider updating to note it's dynamic or removing the specific port number.

📝 Suggested fix
-bun backend  # Supabase functions on :54321
+bun backend  # Supabase functions (port varies per worktree)
🤖 Fix all issues with AI agents
In `@supabase/functions/_backend/triggers/on_version_update.ts`:
- Around line 44-49: The v2 branch currently logs and returns early from the
inner flow but does not exit updateIt, causing handleManifest to still run; make
the v2 path behavior consistent with the non-v2 branch by returning from
updateIt the same way (call and return c.json(BRES) after logging) when
resolveOwnerOrg(c, record) is falsy in v2PathSize, so both branches either abort
before handleManifest or both continue—adjust the v2 block around
resolveOwnerOrg and ensure it returns c.json(BRES) just like the non-v2 else
branch to prevent manifest processing.

In `@supabase/functions/_backend/utils/downloadUrl.ts`:
- Around line 23-28: The code reads X-Forwarded-Host raw into forwardedHost but
uses firstForwardedHeaderValue for X-Forwarded-Proto, causing invalid hosts when
headers are comma-separated; update the handling in downloadUrl.ts to parse
X-Forwarded-Host (and optionally X-Forwarded-Port) with
firstForwardedHeaderValue before using them, then set url.host using the
sanitized host (and port fallback logic) so url.host is never built from a
comma-separated string; modify the variables forwardedHost/forwardedPort usage
where url.host is assigned to reference the parsed values.
🧹 Nitpick comments (3)
supabase/functions/_backend/utils/downloadUrl.ts (1)

17-43: URL mutation side-effect is implicit — consider documenting or returning a new URL.

rewriteLocalEdgeRuntimeUrl mutates url in place but also returns it. Callers (getBundleUrl line 75, getManifestUrl line 87) destructure only finalPath and then reference url directly. This works because of reference semantics, but the side-effect is easy to miss during future maintenance.

A brief JSDoc or an inline comment on the function noting the mutation would help readability.

scripts/supabase-worktree.ts (2)

128-135: parseStatusJson may fail on trailing non-JSON output.

JSON.parse(mixed.slice(idx)) parses from the first { to end-of-string. If supabase status emits any text after the JSON object, parsing will throw. The error is caught upstream in getStatusJson (returns ok: false), so it won't crash — but it will incorrectly report Supabase as not running.

A safer approach would be to extract just the JSON object:

♻️ Suggested improvement
 function parseStatusJson(mixed: string): any {
   const idx = mixed.indexOf('{')
   if (idx < 0)
     throw new Error('Failed to parse supabase status output (no JSON object found).')
-  return JSON.parse(mixed.slice(idx))
+  const lastBrace = mixed.lastIndexOf('}')
+  if (lastBrace < idx)
+    throw new Error('Failed to parse supabase status output (no closing brace found).')
+  return JSON.parse(mixed.slice(idx, lastBrace + 1))
 }

243-246: Allowlist restricts with-env to bun/bunx only — consider documenting the rationale.

This is a reasonable security boundary, but if someone needs to run e.g. node or npx through with-env, they'll get a cryptic error. The error message is good, but a brief code comment explaining why this restriction exists would help future maintainers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 19-20: The package.json has a missing comma after the
"readreplicate:add-table" script entry causing JSON parse/CI failures; edit
package.json and add a trailing comma after the "readreplicate:add-table": "bash
read_replicate/replicate_add_table.sh" line so the subsequent "preview": "vite
preview" entry is valid JSON (verify the scripts block remains well-formed).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@messages/es.json`:
- Around line 496-498: Update the "credits-only-info-link" string to use the
plural term "créditos" for consistency with other Spanish entries; change its
value from "Gestiona tu uso del crédito" to "Gestiona tu uso de créditos" (edit
the JSON value for the key "credits-only-info-link") and verify it matches the
style of "credits-only-info-title" and other credit-related keys.

In `@messages/pt-br.json`:
- Around line 496-498: The link text "credits-only-info-link" uses singular
"crédito" while the rest of the pt-BR messages use plural "créditos"; update the
value of the "credits-only-info-link" key to a plural form (for example
"Gerencie seu uso de créditos" or "Gerencie seus créditos") so it matches the
other keys "credits-only-info-description" and "credits-only-info-title".

In `@package.json`:
- Line 4: Revert the manual change to the "version" field in package.json (the
line setting "version": "12.110.6") so the file returns to its prior state;
remove any committed version bump and ensure no manual edits to package.json's
version remain, since CI/CD/semantic-release manages versioning, CHANGELOG.md,
and deployments automatically.

In `@scripts/update_cloudsql_authorized_networks.sh`:
- Around line 48-69: The option parsing loop currently does blind "shift 2" for
--project, --instance, and --cloudflare-url which fails under set -euo pipefail
when a value is missing; update the case branches for these flags to first
verify that "$2" exists and does not begin with "-" (e.g., [[ -n "${2:-}" &&
${2:0:1} != "-" ]]) and if the check fails print a clear error (including the
flag name) and exit non‑zero, otherwise assign PROJECT_ID, INSTANCE_NAME, or
CF_IPS_URL and then shift 2 as before so missing values are handled gracefully.

In `@src/components/CreditsCta.vue`:
- Around line 31-66: Add the DaisyUI d-btn class to both clickable <button>
elements in the CreditsCta component: the button with v-if="props.creditsOnly"
(the credits-only CTA that includes IconInformationCircle) and the fallback
v-else button (the default "Don't want to upgrade?" CTA). Update each button's
class attribute to include "d-btn" alongside the existing Tailwind classes so
both interactive elements comply with the UI guidelines.

In
`@supabase/migrations/20260214054927_restore_top_up_usage_credits_for_service_role.sql`:
- Around line 16-18: The inline comment mentioning caller `#3` using
supabaseClient is stale; update or remove it in the migration header so it
accurately reflects that the /complete-top-up endpoint in credits.ts now uses
supabaseAdmin (service role) instead of supabaseClient — either change the text
to state "now uses supabaseAdmin (service role)" for caller `#3` or delete the
caller `#3` bullet entirely so the comment no longer misstates the caller
authentication method.
- Around line 23-30: The migration currently grants EXECUTE on the function
top_up_usage_credits to "service_role" only; update the migration to also GRANT
EXECUTE on FUNCTION "public"."top_up_usage_credits"( "p_org_id" "uuid",
"p_amount" numeric, "p_expires_at" timestamp with time zone, "p_source" "text",
"p_source_ref" "jsonb", "p_notes" "text" ) TO "authenticated" so the frontend
caller via supabaseClient (the authenticated role) can execute it; ensure the
GRANT uses the exact function signature used in the file to avoid mismatched
permission statements.
🧹 Nitpick comments (3)
src/stores/organization.ts (1)

396-403: Pre-existing: stripeEnabled check is missing in fetchOrganizations.

The watcher on line 188 includes !stripeEnabledValue as a condition to skip the failure check, but this fetchOrganizations block (line 398) does not account for stripeEnabled. This is a pre-existing inconsistency (not introduced by this PR), but worth noting since you're touching adjacent logic — a future change could unify both paths.

supabase/functions/_backend/private/credits.ts (1)

450-452: Consider granting EXECUTE to authenticated instead of switching to admin SDK.

The coding guidelines state that the admin SDK should only be used for internal operations, triggers, and CRON jobs — not user-facing APIs. The /complete-top-up endpoint is user-facing, so switching to supabaseAdmin here is a guideline deviation.

An alternative approach: grant EXECUTE on top_up_usage_credits to authenticated as well (in the migration), and keep using supabaseClient here. The function itself should enforce any necessary authorization internally, and the upstream checks (JWT + RBAC + Stripe verification) already guard this code path.

That said, if the RPC function performs privileged writes that RLS would otherwise block (e.g., inserting into tables the user shouldn't directly access), then supabaseAdmin is the pragmatic choice — just be aware this bypasses RLS for the entire call.

As per coding guidelines: "Never use the Supabase admin SDK (with service key) for user-facing APIs; always use the client SDK with user authentication to enforce RLS policies; admin SDK should only be used for internal operations, triggers, and CRON jobs."

scripts/update_cloudsql_authorized_networks.sh (1)

141-141: Drop unused row local.

It is declared but never used.

♻️ Suggested cleanup
-  local row

package.json Outdated
"name": "capgo-app",
"type": "module",
"version": "12.110.1",
"version": "12.110.6",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid manual version bumps in package.json.

Line 4 updates the version to 12.110.6, but versioning is CI/CD-managed. Please revert the manual bump.

🐛 Suggested fix
-  "version": "12.110.6",
+  "version": "12.110.1",

Based on learnings: Do not manually deploy or commit version bumps; CI/CD handles version bumping in package.json, CHANGELOG.md generation via semantic-release, and deployment after merge to main.

📝 Committable suggestion

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

Suggested change
"version": "12.110.6",
"version": "12.110.1",
🤖 Prompt for AI Agents
In `@package.json` at line 4, Revert the manual change to the "version" field in
package.json (the line setting "version": "12.110.6") so the file returns to its
prior state; remove any committed version bump and ensure no manual edits to
package.json's version remain, since CI/CD/semantic-release manages versioning,
CHANGELOG.md, and deployments automatically.

Comment on lines 23 to 30
GRANT EXECUTE ON FUNCTION "public"."top_up_usage_credits"(
"p_org_id" "uuid",
"p_amount" numeric,
"p_expires_at" timestamp with time zone,
"p_source" "text",
"p_source_ref" "jsonb",
"p_notes" "text"
) TO "service_role";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find and examine the specific migration file
find . -name "20260214054927_restore_top_up_usage_credits_for_service_role.sql" -type f | head -1 | xargs cat -n

Repository: Cap-go/capgo

Length of output: 1648


🏁 Script executed:

#!/bin/bash
# Search for all references to top_up_usage_credits in migrations and code
rg 'top_up_usage_credits' --glob '*.sql' -B 2 -A 2

Repository: Cap-go/capgo

Length of output: 8487


🏁 Script executed:

#!/bin/bash
# Check for any GRANT statements on this function to understand historical context
rg 'GRANT.*top_up_usage_credits' --glob '*.sql' -B 1 -A 1

Repository: Cap-go/capgo

Length of output: 697


Grant authenticated role as well — migration comment documents authenticated caller that will still fail.

The migration comment explicitly states that top_up_usage_credits is called via supabaseClient (authenticated role) from supabase/functions/_backend/private/credits.ts (line ~450), the frontend complete-top-up endpoint. The comment also states that without this fix, "all three callers fail with 42501: permission denied for function top_up_usage_credits". However, the migration only grants EXECUTE to service_role.

You must also GRANT EXECUTE to authenticated role, or the frontend endpoint will still receive a 42501 error.

🤖 Prompt for AI Agents
In
`@supabase/migrations/20260214054927_restore_top_up_usage_credits_for_service_role.sql`
around lines 23 - 30, The migration currently grants EXECUTE on the function
top_up_usage_credits to "service_role" only; update the migration to also GRANT
EXECUTE on FUNCTION "public"."top_up_usage_credits"( "p_org_id" "uuid",
"p_amount" numeric, "p_expires_at" timestamp with time zone, "p_source" "text",
"p_source_ref" "jsonb", "p_notes" "text" ) TO "authenticated" so the frontend
caller via supabaseClient (the authenticated role) can execute it; ensure the
GRANT uses the exact function signature used in the file to avoid mismatched
permission statements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
supabase/functions/_backend/triggers/on_version_update.ts (2)

213-253: ⚠️ Potential issue | 🟠 Major

Manifest rows without s3_path are never deleted — orphaned data.

The if (entry.s3_path) guard on line 215 means entries where s3_path is falsy are completely skipped: their DB rows survive while the counters are zeroed out (line 260), leaving stale data.

Proposed fix: always delete the manifest row, only conditionally delete from S3
       const promisesDeleteS3 = []
       for (const entry of manifestEntries) {
-        if (entry.s3_path) {
-          promisesDeleteS3.push(
-            // First delete the manifest row from database
-            supabaseAdmin(c)
-              .from('manifest')
-              .delete()
-              .eq('id', entry.id)
-              .then(({ error: deleteError }) => {
+        promisesDeleteS3.push(
+          // First delete the manifest row from database
+          supabaseAdmin(c)
+            .from('manifest')
+            .delete()
+            .eq('id', entry.id)
+            .then(({ error: deleteError }) => {
+              if (deleteError) {
+                cloudlog({ requestId: c.get('requestId'), message: 'error deleting manifest row', id: entry.id, error: deleteError })
+                return null // Signal to skip S3 cleanup
+              }
+              if (!entry.s3_path)
+                return null // No S3 asset to clean up
+              // After deleting, check if any other rows still reference this file
+              return supabaseAdmin(c)
+                .from('manifest')
+                .select('*', { count: 'exact', head: true })
+                .eq('file_name', entry.file_name)
+                .eq('file_hash', entry.file_hash)
+            })
+            .then((v) => {
+              if (!v)
+                return
+              if (v.error) {
+                cloudlog({ requestId: c.get('requestId'), message: 'error checking manifest references', error: v.error })
+                return
+              }
+              const count = v.count ?? 0
+              if (count)
+                return
+              cloudlog({ requestId: c.get('requestId'), message: 'deleted manifest file from S3', s3_path: entry.s3_path })
+              return s3.deleteObject(c, entry.s3_path!)
+            }),
+        )
-                if (deleteError) {
-                  cloudlog({ requestId: c.get('requestId'), message: 'error deleting manifest row', id: entry.id, error: deleteError })
-                  return null // Signal to skip S3 cleanup
-                }
                 ...

300-306: ⚠️ Potential issue | 🟡 Minor

S3 delete failure prevents manifest cleanup.

If s3.deleteObject throws for the main bundle (line 301), deleteManifest at line 328 is never reached. This means a transient S3 error leaves manifest rows and their S3 assets permanently orphaned. Consider whether manifest cleanup should run regardless of the main bundle's S3 outcome.

🤖 Fix all issues with AI agents
In `@scripts/update_cloudsql_authorized_networks.sh`:
- Around line 152-157: The variable declaration "local row" is unused and should
be removed; update the block that declares INSTANCE_NAMES and the while loop
(the "while IFS=$'\t' read -r name region state db_version; do" loop) by
deleting the unused "row" declaration so only the used variables and
INSTANCE_NAMES remain, ensuring no other references to "row" exist elsewhere in
the script.
🧹 Nitpick comments (1)
supabase/functions/_backend/utils/downloadUrl.ts (1)

23-49: Side-effect mutation of the url parameter may surprise future callers.

The function mutates the URL object in place while also returning it. Current callers destructure only finalPath and rely on the implicit mutation of their local url reference. This works but is easy to misuse — a future caller could pass a URL they don't expect to be modified.

Consider either documenting the mutation clearly in the JSDoc, or cloning the URL internally (new URL(url.href)) and returning the copy. Given this is internal local-dev-only code, it's low risk.

Comment on lines 152 to 157
INSTANCE_NAMES=()
local row
while IFS=$'\t' read -r name region state db_version; do
[[ -z "$name" ]] && continue
INSTANCE_NAMES+=("$name")
printf " %2d) %-36s region=%-16s state=%-12s db=%s\n" "${#INSTANCE_NAMES[@]}" "$name" "$region" "$state" "$db_version"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused row variable.

row is declared on line 153 but never read; the while read on line 154 destructures directly into name, region, state, db_version.

Proposed fix
   local instances_json
   instances_json="$(gcloud sql instances list --project="$PROJECT_ID" --format=json)"
 
   INSTANCE_NAMES=()
-  local row
   while IFS=$'\t' read -r name region state db_version; do
📝 Committable suggestion

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

Suggested change
INSTANCE_NAMES=()
local row
while IFS=$'\t' read -r name region state db_version; do
[[ -z "$name" ]] && continue
INSTANCE_NAMES+=("$name")
printf " %2d) %-36s region=%-16s state=%-12s db=%s\n" "${#INSTANCE_NAMES[@]}" "$name" "$region" "$state" "$db_version"
INSTANCE_NAMES=()
while IFS=$'\t' read -r name region state db_version; do
[[ -z "$name" ]] && continue
INSTANCE_NAMES+=("$name")
printf " %2d) %-36s region=%-16s state=%-12s db=%s\n" "${`#INSTANCE_NAMES`[@]}" "$name" "$region" "$state" "$db_version"
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 153-153: row appears unused. Verify use (or export if used externally).

(SC2034)

🤖 Prompt for AI Agents
In `@scripts/update_cloudsql_authorized_networks.sh` around lines 152 - 157, The
variable declaration "local row" is unused and should be removed; update the
block that declares INSTANCE_NAMES and the while loop (the "while IFS=$'\t' read
-r name region state db_version; do" loop) by deleting the unused "row"
declaration so only the used variables and INSTANCE_NAMES remain, ensuring no
other references to "row" exist elsewhere in the script.

@sonarqubecloud
Copy link

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