Skip to content

fix(cli): improve branch provisioning recovery and reconciliation - #203

Open
shobhitsahani wants to merge 9 commits into
InsForge:mainfrom
shobhitsahani:main
Open

fix(cli): improve branch provisioning recovery and reconciliation#203
shobhitsahani wants to merge 9 commits into
InsForge:mainfrom
shobhitsahani:main

Conversation

@shobhitsahani

@shobhitsahani shobhitsahani commented Jul 22, 2026

Copy link
Copy Markdown
  • Fix network error detection so create reconciliation is reachable
  • Add tests for readiness, reconciliation, and delete retry
  • Improve provisioning documentation
  • Preserve existing CLI behavior while improving recovery from transient failures

Summary by cubic

Makes branch provisioning predictable and safer. branch create now waits until the branch actually serves traffic by default and exits non‑zero if provisioning stalls or never serves, with tighter recovery from network interruptions.

  • New Features

    • --wait-ready (default on) waits for the data plane /api/health to be healthy before returning; use --no-wait-ready to skip.
    • Increased readiness polling to 15 minutes and fail fast with clear messages if a branch stays in creating or never starts serving.
  • Bug Fixes

    • Reconciliation only runs on transport-level network failures and adopts a branch only if it matches name, mode, and was created after the request; emits { reconciled: true } in --json.
    • Friendlier “still provisioning” errors for db query and db migrations, with a --wait-ready hint.
    • branch delete retries when busy (creating/merging) for up to ~6 minutes, then reports a clear timeout if still busy.
    • Added tests for create readiness/serving timeouts, reconciliation guards, delete retry, and DB provisioning messaging.

Written for commit 40acf52. Summary will update on new commits.

Review in cubic

Note

Add data plane health polling, delete retry logic, and provisioning error handling to branch commands

  • Adds --wait-ready flag (default true) to branch create that polls the branch /api/health endpoint every 5s for up to 15 minutes, waiting for a healthy/ok status before returning.
  • Adds network-error reconciliation to branch create: if createBranchApi fails with a network-style error, the CLI calls listBranchesApi to check if the branch was actually created and returns { branch, reconciled: true } in JSON mode.
  • Adds retry logic to branch delete: if the branch is busy (creating/merging), the CLI polls getBranchApi every 30s for up to 6 minutes until the branch is deletable, then retries the delete.
  • Adds provisioning-aware error handling to db query and db migrations subcommands: when running against a branch, network errors indicative of an unready data plane now emit a friendly message with a --wait-ready hint and exit with code 1.
  • Behavioral Change: branch create now waits for data plane readiness by default; callers relying on fast non-blocking creation must pass --wait-ready=false.

Macroscope summarized 34b302e.

Summary by CodeRabbit

  • New Features
    • Branch creation now waits for data-plane health by default, with a --no-wait-ready option, and supports adopting/reconciling a branch after connectivity interruptions.
    • Branch deletion automatically retries when the branch is busy (creating/merging), with a timeout and actionable messaging.
    • Database commands on branch projects now show clearer “still provisioning” guidance.
  • Bug Fixes
    • Improved network/provisioning error detection and reporting, including consistent JSON/plain-text output and exit behavior.
  • Documentation
    • Updated README examples for branch create/delete, including wait/retry timing and options.

iPLAYCAFE and others added 3 commits July 22, 2026 16:56
Three defects that share one theme: the command reports success on signals that
do not mean what a caller needs them to mean. Found while automating branch
creation against ap-southeast; details and measurements in InsForge/InsForge#1790.

1. `ready` is a control-plane state, not readiness.

   `pollUntilReady` returns the moment `branch_state === 'ready'` and never
   contacts the branch's own host. But `branch_state` flips when the
   provisioning job returns, while the instance is still coming up — every
   request to https://<appkey>.<region>.insforge.app resets until it does.
   Measured on ap-southeast: the host started serving at t+2m in one case and
   t+11.5m in another, `branch_state` reading 'ready' the whole time.

   Because create then auto-switches the directory onto that host, the failure
   surfaces on the user's NEXT command rather than here, as
   {"error":"fetch failed","code":"UNKNOWN_ERROR"} from whatever they ran.

   Now: after the control plane says ready, poll GET /api/health on the branch
   itself until it answers, and say so in the spinner. If it never answers
   within the budget the command reports that honestly instead of claiming
   success — the branch still exists and its name and id are still printed,
   because it is real and it is billing.

2. The 5-minute poll ceiling was below the observed provisioning time.

   POLL_TIMEOUT_MS was 5 minutes, so the 11.5-minute branch was reported as
   "still in 'creating' state" when it was simply not finished. Raised to 15,
   with a separate 10-minute budget for the data-plane wait.

3. A failed create can leave a live branch behind.

   `createBranchApi` carries no idempotency key. A transport failure on the
   RESPONSE leg — the POST arrived and the branch was created — throws before
   `created` is bound, so the CLI exits non-zero with no id and no name while a
   branch exists and bills. We hit exactly this:

     $ insforge branch create <name> --mode schema-only --no-switch
     {"error":"Connection to api.insforge.dev was reset. A proxy, VPN, or
      firewall may be interfering."}   # exit 1
     $ insforge branch list            # the branch is there, state "creating"

   Now: on a create failure, ask `branch list` — a control-plane call that still
   works while the branch's own host is unreachable — whether the branch exists
   under that name, and adopt it if so. The original error is rethrown unchanged
   when nothing was created.

Also: `ossFetch` called `fetch` unguarded, unlike `platformFetch`, so a dead
data plane surfaced as the generic UNKNOWN_ERROR instead of naming the host.
The new `probeBackendHealth` routes its errors through `formatFetchError`, so
"Connection to <host> was reset" is what a caller sees.

Tests cover the not-serving path, the adopt path, and that a genuine failure
with nothing created still exits non-zero. The existing create tests needed a
mock for the new probe — an unmocked one makes a real request to a fake host and
then polls, so beforeEach resets it explicitly (clearAllMocks keeps
implementations, which would otherwise leak an unreachable branch into every
later test).

Agent skills: `insforge-cli/references/branch/overview.md` says a branch takes
30-120s and that `ready` means "usable — can be switched, modified, merged, or
reset", and its post-create checklist goes straight to `functions deploy`. Both
are worth updating in InsForge/agent-skills once the timing here is confirmed;
happy to open that PR alongside if you want it.
…host never serves

Both review findings are real; fixing rather than arguing.

1. Adoption was too broad (greptile P1/security, cubic P1).

   The catch adopted a same-name branch after ANY create failure, so a
   duplicate-name rejection — a refusal, not a lost response — could switch the
   caller into a pre-existing branch with a different mode and different data.

   Two guards now, either of which is sufficient:
   - only a TAGGED transport failure is eligible. platformFetch's fetch-level
     catch now throws CLIError with code NETWORK_ERROR, so "the server said no"
     and "we never heard back" are finally distinguishable by callers. Every
     HTTP/API rejection rethrows untouched, and the test asserts that
     listBranchesApi is not even called in that case.
   - the candidate must have been created at or after the moment we sent the
     request, so a pre-existing same-name branch can never be adopted. A 60s
     skew allowance keeps a genuinely-just-created branch eligible when the
     local clock differs from the control plane's; being slightly wide risks
     adopting a branch someone made seconds ago under the same name, while
     being too narrow re-opens the orphaned-billing bug this exists to fix.

2. A branch that never serves exited 0 (greptile P1, cubic P1).

   I stopped the spinner with an error frame and left it there: the process
   still exited 0, --json still emitted a ready-looking branch with no serving
   field, and the non-JSON path still advised re-sourcing the env. That is
   exactly the "exit 0 does not mean ready" defect this PR is meant to remove,
   reproduced inside the fix for it.

   Now: `serving` is tracked separately from `provisioned`, --json emits
   { branch, serving }, the env hint only prints when the host actually answers,
   and the command throws afterwards so the exit is non-zero. The identity is
   emitted BEFORE the throw on purpose — the branch exists and is billing, so a
   caller has to be able to find and delete it even as the command fails.

Three tests added: an API rejection is not adopted, a branch predating the
request is not adopted, and the never-serving path exits 1 while still printing
the branch id and serving:false.
- Fix network error detection so create reconciliation is reachable
- Add tests for readiness, reconciliation, and delete retry
- Improve provisioning documentation
- Preserve existing CLI behavior while improving recovery from transient failures
@agent-zhang-beihai

Copy link
Copy Markdown
Contributor

Thanks for the PR, @shobhitsahani! A quick note on our workflow: we ask contributors to open an issue first, get it assigned, then submit a PR that links it (e.g. "Closes #123"). This PR isn't linked to any issue. It'll still be reviewed, but please open an issue and claim it (comment that you'd like it assigned to you) so the work is tracked.

@agent-zhang-beihai agent-zhang-beihai Bot added the needs-issue PR isn't linked to any issue — open and claim an issue first, then link it label Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Branch creation now waits for data-plane health and reconciles interrupted requests. Branch deletion retries busy-state failures. Database commands identify branch provisioning errors and emit branch-specific output.

Changes

Branch lifecycle resilience

Layer / File(s) Summary
Provisioning and network error foundations
src/lib/api/platform.ts, src/lib/api/oss.ts
Adds network-error classification, provisioning-message helpers, and non-throwing backend health probing.
Branch creation readiness and reconciliation
src/commands/branch/create.ts, src/commands/branch/create.test.ts
Adds --wait-ready health polling, transport-failure adoption, network reconciliation, and coverage for readiness and reconciliation paths.
Busy branch deletion retry
src/commands/branch/delete.ts, src/commands/branch/delete.test.ts
Polls creating or merging branches after busy deletion failures, then retries deletion or reports a timeout. The test file contains unresolved merge-conflict markers.
Provisioning-aware database errors
src/commands/db/query.ts, src/commands/db/query.test.ts, src/commands/db/migrations.ts
Adds branch-specific JSON or text handling for provisioning failures across query and migration commands.
Branch lifecycle documentation
README.md
Documents readiness polling, interruption recovery, and busy deletion retries.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • InsForge/CLI#201: Shares the branch creation readiness polling and transport-failure adoption changes.

Suggested reviewers: fermionic-lyu, jwfing

Poem

A rabbit watched the branches grow,
While health checks whispered, “Ready—go!”
Busy limbs delayed the prune,
Then retries made deletion run.
Provisioning errors found their voice.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main CLI change: improved branch provisioning recovery and reconciliation.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR improves recovery and readiness handling for branch provisioning. The main changes are:

  • Adds data-plane readiness polling to branch creation.
  • Reconciles branch creation after ambiguous network failures.
  • Retries deletion while a branch is busy.
  • Adds provisioning-aware errors to database commands.
  • Expands tests and provisioning documentation.

Confidence Score: 5/5

No additional qualifying issues were found for this follow-up review.

  • No new blocking findings met the follow-up review criteria.

Reviews (2): Last reviewed commit: "fix(cli): rebase onto #201 base and reso..." | Re-trigger Greptile

Comment thread src/commands/branch/create.ts
Comment thread src/commands/branch/create.ts
Comment thread src/commands/branch/create.ts
Comment thread src/lib/api/oss.ts

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

🧹 Nitpick comments (3)
src/commands/branch/delete.test.ts (1)

176-195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test name is misleading and overlaps test at Lines 197-214.

This case asserts deleteBranchApi is called twice — i.e. the retry flow — but is named as if it directly verifies isBusyError. isBusyError is not exported, so it's never unit-tested in isolation. Either rename this to reflect the retry behavior it actually covers (and drop the near-duplicate of the "busy then ready" test below), or export isBusyError and add a true unit test over its message cases.

🤖 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 `@src/commands/branch/delete.test.ts` around lines 176 - 195, Rename the test
describing the `deleteBranchApi` call count to reflect that it verifies retry
behavior after a provisioning-busy error, and remove the overlapping “busy then
ready” test below. Keep the existing retry assertions and message-case coverage
intact; do not treat this test as direct `isBusyError` unit coverage unless you
explicitly export that symbol and add isolated tests.
src/commands/branch/delete.ts (1)

15-22: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Drop the redundant currently busy clause busy already covers it; this path only has message text to inspect, so there isn’t a stable code/statusCode to key off here.

🤖 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 `@src/commands/branch/delete.ts` around lines 15 - 22, Remove the redundant
msg.includes('currently busy') condition from isBusyError, while preserving the
existing CLIError check and the busy/creating/merging message checks.
src/commands/db/migrations.ts (1)

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

Extract the duplicated branch-provisioning catch handler. The same block — load getProjectConfig(), check branched_from != null, call isProvisioningError, emit BRANCH_PROVISIONING JSON/text via buildProvisioningErrorMessage, then process.exit(1) — is copied five times. Extract a single helper (e.g. handleBranchProvisioningError(err, json): boolean in src/lib/api/oss.ts alongside the existing helpers) and call it at each site so message/format/exit behavior stays consistent.

  • src/commands/db/migrations.ts#L137-149: replace inline block in migrations list catch with the shared helper.
  • src/commands/db/migrations.ts#L218-230: replace inline block in migrations fetch catch with the shared helper.
  • src/commands/db/migrations.ts#L277-289: replace inline block in migrations new catch with the shared helper.
  • src/commands/db/migrations.ts#L470-482: replace inline block in migrations up catch with the shared helper.
  • src/commands/db/query.ts#L45-59: replace inline block in db query catch with the shared helper.
🤖 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 `@src/commands/db/migrations.ts` at line 1, The branch-provisioning error
handling is duplicated across five command catch blocks. Extract the shared
getProjectConfig/branched_from/isProvisioningError and BRANCH_PROVISIONING
output logic into handleBranchProvisioningError(err, json): boolean alongside
the existing helpers in oss.ts, preserving message formatting and
process.exit(1); then replace the inline handlers in the migrations list, fetch,
new, and up catches plus the db query catch with calls to the helper.
🤖 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 `@src/commands/branch/create.ts`:
- Around line 119-140: Update the branch creation error handling around
isNetworkError and the provisioned branch to handle BRANCH_DATA_PLANE_TIMEOUT
explicitly. Since this error occurs after provisioning, remove it from the
reconciliation-only network-error check and surface dedicated data-plane
readiness timeout guidance instead of the generic context-switch message.
- Around line 54-55: Change the wait-ready option declaration in the branch
creation command to support opting out, using a negated option or equivalent
paired flag so opts.waitReady can be false when requested. Preserve the existing
default behavior of waiting for the data plane when no opt-out is provided.

In `@src/lib/api/oss.ts`:
- Around line 62-66: Update buildProvisioningErrorMessage so its provisioning
duration guidance matches the 15-minute value used by HEALTH_CHECK_TIMEOUT_MS
and the timeout error in create.ts. Change only the user-facing duration text,
preserving the existing branch-specific wording and retry instructions.

---

Nitpick comments:
In `@src/commands/branch/delete.test.ts`:
- Around line 176-195: Rename the test describing the `deleteBranchApi` call
count to reflect that it verifies retry behavior after a provisioning-busy
error, and remove the overlapping “busy then ready” test below. Keep the
existing retry assertions and message-case coverage intact; do not treat this
test as direct `isBusyError` unit coverage unless you explicitly export that
symbol and add isolated tests.

In `@src/commands/branch/delete.ts`:
- Around line 15-22: Remove the redundant msg.includes('currently busy')
condition from isBusyError, while preserving the existing CLIError check and the
busy/creating/merging message checks.

In `@src/commands/db/migrations.ts`:
- Line 1: The branch-provisioning error handling is duplicated across five
command catch blocks. Extract the shared
getProjectConfig/branched_from/isProvisioningError and BRANCH_PROVISIONING
output logic into handleBranchProvisioningError(err, json): boolean alongside
the existing helpers in oss.ts, preserving message formatting and
process.exit(1); then replace the inline handlers in the migrations list, fetch,
new, and up catches plus the db query catch with calls to the helper.
🪄 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: 517306a1-786d-46fc-9272-89ecfec19f22

📥 Commits

Reviewing files that changed from the base of the PR and between 64c0859 and 34b302e.

📒 Files selected for processing (8)
  • src/commands/branch/create.test.ts
  • src/commands/branch/create.ts
  • src/commands/branch/delete.test.ts
  • src/commands/branch/delete.ts
  • src/commands/db/migrations.ts
  • src/commands/db/query.test.ts
  • src/commands/db/query.ts
  • src/lib/api/oss.ts

Comment thread src/commands/branch/create.ts
Comment on lines +119 to +140
// Check if this is a network error (fetch failed, ECONNRESET, etc.)
// Match both raw undici error messages AND the formatted output of
// formatFetchError (used by platformFetch), so reconciliation is
// reachable regardless of which layer surfaces the error.
// If so, attempt to reconcile by checking if the branch was actually created
const isNetworkError = err instanceof CLIError &&
(err.message.includes('fetch failed') ||
err.message.includes('ECONNRESET') ||
err.message.includes('ETIMEDOUT') ||
err.message.includes('ENOTFOUND') ||
err.message.includes('ECONNREFUSED') ||
err.message.includes('UND_ERR_CONNECT_TIMEOUT') ||
err.message.includes('UND_ERR_SOCKET') ||
err.message.includes('timeout') ||
// Formatted messages from formatFetchError (used by platformFetch)
err.message.includes('was reset') ||
err.message.includes('was refused') ||
err.message.includes('timed out') ||
err.message.includes('Cannot resolve') ||
err.message.includes('Network error contacting') ||
err.message.includes('TLS certificate error') ||
err.code === 'BRANCH_DATA_PLANE_TIMEOUT');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

BRANCH_DATA_PLANE_TIMEOUT in isNetworkError is unreachable here, and the timeout produces a misleading message.

waitForDataPlaneReady only runs after provisioned becomes true (Line 100), so its BRANCH_DATA_PLANE_TIMEOUT error can only surface with provisioned === true. But reconciliation is gated on !provisioned (Line 142), so this err.code check never contributes. Worse, that timeout then falls through to the if (provisioned) branch (Line 167), telling the user "switching context failed — run insforge branch switch to retry", which misdescribes a data-plane readiness timeout. Consider handling the timeout case explicitly so the surfaced guidance matches the actual failure.

🤖 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 `@src/commands/branch/create.ts` around lines 119 - 140, Update the branch
creation error handling around isNetworkError and the provisioned branch to
handle BRANCH_DATA_PLANE_TIMEOUT explicitly. Since this error occurs after
provisioning, remove it from the reconciliation-only network-error check and
surface dedicated data-plane readiness timeout guidance instead of the generic
context-switch message.

Comment thread src/lib/api/oss.ts
Comment on lines +62 to +66
export function buildProvisioningErrorMessage(branchName?: string): string {
const base = 'Branch is still provisioning (this can take up to ~12 minutes).';
const branchPart = branchName ? ` Branch: ${branchName}.` : '';
return `${base}${branchPart} Retry shortly, or create the branch with \`--wait-ready\` to block until it's usable.`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Minor: message says "~12 minutes" while the health-check timeout in create.ts is 15 minutes.

HEALTH_CHECK_TIMEOUT_MS and the timeout error both reference 15 minutes; this guidance says ~12. Align the numbers to avoid confusing users.

🤖 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 `@src/lib/api/oss.ts` around lines 62 - 66, Update
buildProvisioningErrorMessage so its provisioning duration guidance matches the
15-minute value used by HEALTH_CHECK_TIMEOUT_MS and the timeout error in
create.ts. Change only the user-facing duration text, preserving the existing
branch-specific wording and retry instructions.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

5 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/commands/branch/delete.test.ts">

<violation number="1" location="src/commands/branch/delete.test.ts:176">
P3: Test name claims to cover 'busy, creating, and merging' message patterns but only tests a 'busy' message. Either rename to match what's actually tested (e.g., 'retries deletion on busy error') or add test cases for 'creating' and 'merging' messages to match the name.</violation>
</file>

<file name="src/lib/api/oss.ts">

<violation number="1" location="src/lib/api/oss.ts:23">
P3: The new classifier can regress without a test catching which network causes are accepted versus ordinary or TLS failures. Direct unit cases for supported cause codes, unsupported causes, and HTTP `CLIError` messages would make this recovery behavior verifiable.</violation>

<violation number="2" location="src/lib/api/oss.ts:32">
P3: Network failure classification is duplicated between `isProvisioningError` and `formatFetchError`, so adding or changing a supported cause requires edits in two places and can produce inconsistent CLI behavior. Sharing the codes or classifier would keep branch recovery aligned with the existing fetch-error handling.</violation>
</file>

<file name="src/commands/branch/create.ts">

<violation number="1" location="src/commands/branch/create.ts:54">
P2: Every `branch create` now performs the data-plane health wait by default, potentially blocking for 15 minutes and changing the existing command behavior; the declared positive flag also provides no way to disable that default. The option should default to false/undefined so the health wait only runs when `--wait-ready` is supplied.</violation>
</file>

<file name="src/commands/db/query.test.ts">

<violation number="1" location="src/commands/db/query.test.ts:60">
P2: Heavy boilerplate duplication between the two test cases (~20 lines repeated). Extract the console.error capture, process.exit mock, and try/finally teardown into a shared helper or beforeEach/afterEach so new tests don't need to copy the same pattern.</violation>
</file>

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

Re-trigger cubic

Comment thread src/commands/branch/create.ts
Comment thread src/commands/db/query.ts
Comment thread src/lib/api/oss.ts
Comment thread src/commands/branch/create.ts
Comment thread src/commands/branch/create.ts
Comment thread src/commands/db/query.ts
Comment thread src/commands/branch/delete.ts
Comment thread src/commands/branch/delete.ts
Comment thread src/lib/api/oss.ts
* This detects network-level failures (ECONNRESET, fetch failed, timeout)
* that occur when the branch's data plane isn't ready yet.
*/
export function isProvisioningError(err: unknown): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The new classifier can regress without a test catching which network causes are accepted versus ordinary or TLS failures. Direct unit cases for supported cause codes, unsupported causes, and HTTP CLIError messages would make this recovery behavior verifiable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/api/oss.ts, line 23:

<comment>The new classifier can regress without a test catching which network causes are accepted versus ordinary or TLS failures. Direct unit cases for supported cause codes, unsupported causes, and HTTP `CLIError` messages would make this recovery behavior verifiable.</comment>

<file context>
@@ -15,6 +15,56 @@ function requireProjectConfig(): ProjectConfig {
+ * This detects network-level failures (ECONNRESET, fetch failed, timeout)
+ * that occur when the branch's data plane isn't ready yet.
+ */
+export function isProvisioningError(err: unknown): boolean {
+  if (!(err instanceof Error)) return false;
+  const msg = err.message.toLowerCase();
</file context>

Comment thread src/lib/api/oss.ts
: '';

// Network errors that indicate the data plane isn't ready
const provisioningCodes = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Network failure classification is duplicated between isProvisioningError and formatFetchError, so adding or changing a supported cause requires edits in two places and can produce inconsistent CLI behavior. Sharing the codes or classifier would keep branch recovery aligned with the existing fetch-error handling.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/api/oss.ts, line 32:

<comment>Network failure classification is duplicated between `isProvisioningError` and `formatFetchError`, so adding or changing a supported cause requires edits in two places and can produce inconsistent CLI behavior. Sharing the codes or classifier would keep branch recovery aligned with the existing fetch-error handling.</comment>

<file context>
@@ -15,6 +15,56 @@ function requireProjectConfig(): ProjectConfig {
+    : '';
+  
+  // Network errors that indicate the data plane isn't ready
+  const provisioningCodes = [
+    'econnreset',
+    'etimedout',
</file context>

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review — fix(cli): improve branch provisioning recovery and reconciliation

Summary: Solid, well-commented resiliency work (data-plane readiness poll, create reconciliation, delete retry, provisioning-aware DB errors), but the new --wait-ready default-on behavior ships without a working way to turn it off, which blocks merge.

Requirements context

No matching spec/plan for branch provisioning exists under docs/specs/ (this repo uses docs/specs/, not docs/superpowers/). The closest doc — docs/specs/2026-04-17-db-migrations-command-design.md — covers the db migrations surface and states user-facing failures should use CLIError (relevant to a Suggestion below). Otherwise assessed against the PR description and the surrounding code.


Critical

functionality — --wait-ready is default-on with no working way to disable it (src/commands/branch/create.ts:54)

The flag is registered as a positive boolean defaulting to true:

.option('--wait-ready', 'Wait for the branch data plane to be fully ready (up to 15 min)', true)

The PR description says the wait can be disabled "with --no-wait-ready" / --wait-ready=false, but neither works with commander (v13). Commander only creates a negation when a --no-<name> option is explicitly defined — a positive-only flag has no auto-generated negation (the docs example shows --sauceerror: unknown option '--sauce' when only --no-sauce is declared; the reverse is symmetric). So:

  • insforge branch create x --no-wait-readyerror: unknown option '--no-wait-ready'
  • insforge branch create x --wait-ready=false → commander rejects a value on a boolean flag

Net effect: branch create now always blocks on the data-plane health poll (up to 15 min, on top of the existing ~5 min control-plane poll) with no escape hatch. That breaks existing non-blocking/scripted create behavior and directly contradicts the documented opt-out. No test covers the disable path, so it went unnoticed.

Fix: declare the negatable form, e.g. .option('--no-wait-ready', 'Do not wait for the branch data plane to be ready') (this alone yields opts.waitReady defaulting to true), and add a test that passes --no-wait-ready and asserts the health poll is skipped.


Suggestion

functionality — data-plane timeout is reported as a context-switch failure (src/commands/branch/create.ts:100-104, :140, :167-171)
When waitForDataPlaneReady throws BRANCH_DATA_PLANE_TIMEOUT, provisioned is already true, so the catch skips reconciliation (if (!provisioned && isNetworkError)) and falls into the if (provisioned) branch, printing "Branch '<name>' is ready, but switching context failed — run insforge branch switch" — which is misleading (the switch never ran; the health wait timed out). Consequently the err.code === 'BRANCH_DATA_PLANE_TIMEOUT' clause in the isNetworkError list is effectively unreachable. Consider handling the timeout with its own message.

software engineering (tests) — coverage gaps around the new behavior

  • No test for the --no-wait-ready disable path (would have caught the Critical above).
  • No test for waitForDataPlaneReady on an unhealthy/timeout response.
  • No test for the delete "still busy after 6 min" give-up path (delete.ts:44-54).
  • The PR says tests were added for "DB provisioning messaging," but only db query is covered (query.test.ts) — the identical block added to all four db migrations subcommands (migrations.ts) has no test.

software engineering — duplicated provisioning-error block (src/commands/db/migrations.ts:137-149, 218-230, 277-289, 470-482)
The same ~13-line isBranch && isProvisioningError(err)console.error + process.exit(1) block is copy-pasted four times (and a fifth time in query.ts:47-59). Extract a shared helper (e.g. handleBranchProvisioningError(err, json, config)). Related: this path bypasses the existing handleError convention with a direct console.error/process.exit, which diverges from how the rest of the CLI (and the db-migrations spec's "use CLIError") reports failures.

functionality — isBusyError matches error text too loosely (src/commands/branch/delete.ts:15-22)
msg.includes('creating') || msg.includes('merging') || msg.includes('busy') will treat any error message that merely contains those substrings (e.g. a server error mentioning "creating") as a busy state and enter the 6-minute retry loop. Prefer matching a server-provided error code/statusCode over free-text.

functionality — 'was refused' never matches the formatted error (src/commands/branch/create.ts:135)
formatFetchError emits "Connection to <host> refused." for ECONNREFUSED (no "was"), so err.message.includes('was refused') never matches the formatted message ('was reset', 'timed out', 'Cannot resolve' do match their counterparts). Low impact — a refused connection generally means the branch was never created, so reconciliation isn't needed — but the clause is misleading; align it with the actual formatFetchError output.


Information

  • src/commands/branch/delete.ts:37elapsedSec is computed but never used. Not CI-blocking (@typescript-eslint/no-unused-vars is configured as warn and lint doesn't set --max-warnings 0), but it's dead code.
  • src/commands/branch/create.ts:159,194 — the reconciliation success path calls await shutdownAnalytics() before return, and the outer finally calls it again. Harmless if idempotent, but redundant.
  • src/commands/branch/create.ts:18 — the health URL hardcodes https://<appkey>.<region>.insforge.app, ignoring --api-url. Acceptable since branches are cloud-only, but worth a comment.
  • Reconciliation non-JSON path stops the spinner with error code 1 (red frame) but the command exits 0; minor UX inconsistency for a branch that did get created.
  • The PR body lists "Improve provisioning documentation," but the diff changes only .ts files (inline comments + error-message hints); no doc files were touched.

Security: No security-relevant concerns — no new secrets logged, the health endpoint is unauthenticated by design, and no new user input reaches SQL/shell.

Performance: No concerns beyond the behavioral one above — both new poll loops are bounded (health ≤ ~180 sequential requests over 15 min; delete ≤ 12 over 6 min) with no N+1 or hot-path work.


Verdict: request_changes

One Critical (no working way to disable the new default-on 15-minute wait). The reconciliation, delete-retry, and provisioning-error mechanics are otherwise sound; addressing the negation flag (plus a test for it) is the main blocker.

…OR_CODE export

Addresses the review on InsForge#201 (jwfing, approved):

- Functionality: `branch create` now exits non-zero when the branch never
  reaches 'ready' within the poll budget, matching the existing ready-but-
  not-serving exit. Both outcomes are "not usable", and this PR's goal is that
  success means usable — so automation reading the exit code must not see 0 for
  a branch stuck provisioning. Test added (getBranchApi stuck in 'creating' →
  exit 1), with the shared mock impl restored so it can't leak the full poll
  budget into later tests.
- Software engineering: moved `export const NETWORK_ERROR_CODE` below the import
  block in platform.ts (it was interleaved between two imports — legal via
  hoisting, but conventionally declarations sit under the imports).

The `/api/health` "listening ≠ usable" note is acknowledged as the best
client-side signal available today and left as the PR's documented open
question, to key off a data-plane-scoped signal if one lands cloud-side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
iPLAYCAFE and others added 5 commits July 23, 2026 06:38
…it-capture test helper

Addresses the two follow-up reviews on InsForge#201:

- cubic (P2): a transport failure whose response leg is lost could, in the 60s
  skew window, adopt a collaborator's SAME-NAME branch — and a default --switch
  would then move local context onto it. createBranchOrAdopt now also requires
  the candidate's mode to match the requested mode, narrowing the collision to an
  even more specific coincidence (same name AND same mode AND the same ~60s AND
  our transport failure). The real fix is a server-issued idempotency token
  (InsForge/InsForge#1790); this is the tightest client-side guard until then.
  Test added: a same-name, different-mode branch in the window is NOT adopted.

- CodeRabbit (nitpick): extracted `withCapturedExit(fn)` for the repeated
  process.exit/stderr override-restore boilerplate, and applied it to the tests
  this PR added (incl. the fake-timer provisioning-timeout one). The older
  pre-existing blocks are left as-is to keep this diff scoped to the PR's surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…readiness

fix(branch): make `branch create` success mean the branch is usable
- Fix network error detection so create reconciliation is reachable
- Add tests for readiness, reconciliation, and delete retry
- Improve provisioning documentation
- Preserve existing CLI behavior while improving recovery from transient failures

@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

Caution

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

⚠️ Outside diff range comments (2)
src/commands/db/query.ts (1)

53-65: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve the healthy-branch check before reporting provisioning.

These inline replacements remove handleBranchProvisioningError’s /api/health check. Since isProvisioningError classifies generic fetch failed errors, a transient failure against a healthy branch is now incorrectly emitted as BRANCH_PROVISIONING instead of the real network error. Extract/reuse a branch-aware helper that retains both the branch guard and health verification.

  • src/commands/db/query.ts#L53-L65: route provisioning classification through the health-verified path.
  • src/commands/db/migrations.ts#L145-L154: do the same for migrations list.
  • src/commands/db/migrations.ts#L230-L239: do the same for migrations fetch.
  • src/commands/db/migrations.ts#L293-L302: do the same for migrations new.
  • src/commands/db/migrations.ts#L490-L499: do the same for migrations up.
🤖 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 `@src/commands/db/query.ts` around lines 53 - 65, Preserve the health-verified
branch provisioning classification by extracting or reusing a branch-aware
helper that checks both the branch guard and /api/health before reporting
BRANCH_PROVISIONING. Update the provisioning error paths in
src/commands/db/query.ts:53-65, src/commands/db/migrations.ts:145-154,
src/commands/db/migrations.ts:230-239, src/commands/db/migrations.ts:293-302,
and src/commands/db/migrations.ts:490-499 to use this helper, while leaving
healthy-branch transient fetch failures as their original network errors.
src/commands/branch/delete.ts (1)

11-41: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Resolve the unfinished merge before merging this PR.

The committed <<<<<<<, =======, and >>>>>>> markers make both production code and tests unparsable.

  • src/commands/branch/delete.ts#L11-L41: select and retain one busy-error implementation.
  • src/commands/branch/delete.ts#L54-L79: resolve the polling-loop implementation.
  • src/commands/branch/delete.ts#L94-L133: resolve the retry helper implementation.
  • src/commands/branch/delete.ts#L163-L174: resolve the command action implementation.
  • src/commands/db/migrations.ts#L4-L8: retain one import declaration.
  • src/commands/db/migrations.ts#L142-L157: resolve the migrations list error handler.
  • src/commands/db/migrations.ts#L226-L242: resolve the migrations fetch error handler.
  • src/commands/db/migrations.ts#L289-L305: resolve the migrations new error handler.
  • src/commands/db/migrations.ts#L486-L502: resolve the migrations up error handler.
  • src/commands/db/query.test.ts#L5-L33: resolve the OSS API mock.
  • src/commands/db/query.test.ts#L66-L129: resolve the first test’s capture/assertion setup.
  • src/commands/db/query.test.ts#L149-L193: resolve the second test’s capture/assertion setup.
  • src/commands/db/query.ts#L2-L6: retain one import declaration.
  • src/commands/db/query.ts#L49-L68: resolve the query error handler.
🤖 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 `@src/commands/branch/delete.ts` around lines 11 - 41, Resolve the unfinished
merge by removing every conflict marker and selecting one coherent
implementation at all affected sites: retain one busy-error implementation and
reconcile the polling loop, retry helper, and command action in
src/commands/branch/delete.ts at lines 11-41, 54-79, 94-133, and 163-174; retain
one import and reconcile the migrations list, fetch, new, and up error handlers
in src/commands/db/migrations.ts at lines 4-8, 142-157, 226-242, 289-305, and
486-502; reconcile the OSS API mock and both test capture/assertion setups in
src/commands/db/query.test.ts at lines 5-33, 66-129, and 149-193; retain one
import and reconcile the query error handler in src/commands/db/query.ts at
lines 2-6 and 49-68. Ensure all resulting production code and tests parse and
preserve the intended behavior.

Source: Linters/SAST tools

🤖 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 `@src/commands/branch/create.ts`:
- Around line 3-13: Resolve all remaining merge conflicts by retaining the HEAD
implementation and removing conflict markers and incoming-side code. In
src/commands/branch/create.ts at lines 3-13, 23-74, 82-86, and 141-158, preserve
NETWORK_ERROR_CODE, probeBackendHealth, --no-wait-ready, and the
waitUntilServing branch; in src/lib/api/oss.ts lines 69-120, retain
handleBranchProvisioningError; in src/commands/branch/delete.test.ts lines
216-276, retain the “still busy after max retry time” test; and in
src/commands/branch/create.test.ts lines 33-57, 345-384, 390-391, 690, and
695-846, retain the HEAD readiness/adoption tests. Ensure no conflict markers
remain and all files parse and compile.

---

Outside diff comments:
In `@src/commands/branch/delete.ts`:
- Around line 11-41: Resolve the unfinished merge by removing every conflict
marker and selecting one coherent implementation at all affected sites: retain
one busy-error implementation and reconcile the polling loop, retry helper, and
command action in src/commands/branch/delete.ts at lines 11-41, 54-79, 94-133,
and 163-174; retain one import and reconcile the migrations list, fetch, new,
and up error handlers in src/commands/db/migrations.ts at lines 4-8, 142-157,
226-242, 289-305, and 486-502; reconcile the OSS API mock and both test
capture/assertion setups in src/commands/db/query.test.ts at lines 5-33, 66-129,
and 149-193; retain one import and reconcile the query error handler in
src/commands/db/query.ts at lines 2-6 and 49-68. Ensure all resulting production
code and tests parse and preserve the intended behavior.

In `@src/commands/db/query.ts`:
- Around line 53-65: Preserve the health-verified branch provisioning
classification by extracting or reusing a branch-aware helper that checks both
the branch guard and /api/health before reporting BRANCH_PROVISIONING. Update
the provisioning error paths in src/commands/db/query.ts:53-65,
src/commands/db/migrations.ts:145-154, src/commands/db/migrations.ts:230-239,
src/commands/db/migrations.ts:293-302, and src/commands/db/migrations.ts:490-499
to use this helper, while leaving healthy-branch transient fetch failures as
their original network errors.
🪄 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 Plus

Run ID: 8456e85d-6211-45bc-a5b0-023c5ce3083e

📥 Commits

Reviewing files that changed from the base of the PR and between 34b302e and 40acf52.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • README.md
  • src/commands/branch/create.test.ts
  • src/commands/branch/create.ts
  • src/commands/branch/delete.test.ts
  • src/commands/branch/delete.ts
  • src/commands/db/migrations.ts
  • src/commands/db/query.test.ts
  • src/commands/db/query.ts
  • src/lib/api/oss.ts
  • src/lib/api/platform.ts

Comment on lines +3 to +13
<<<<<<< HEAD
import {
createBranchApi,
getBranchApi,
listBranchesApi,
NETWORK_ERROR_CODE,
} from '../../lib/api/platform.js';
import { probeBackendHealth } from '../../lib/api/oss.js';
=======
import { createBranchApi, getBranchApi, listBranchesApi } from '../../lib/api/platform.js';
>>>>>>> 34b302ebc5c301be89edb5a9c7e75ac702eb55ca

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Unresolved merge conflicts across four files (incomplete rebase). Each file still carries <<<<<<< HEAD / ======= / >>>>>>> 34b302eb… markers, so none of them parse or compile. In every case the HEAD side is the intended resolution (it embodies the --no-wait-ready, waitUntilServing/probeBackendHealth, and handleBranchProvisioningError behavior this PR describes). Resolve by keeping HEAD and deleting the markers plus the incoming side.

  • src/commands/branch/create.ts#L3-L13: also resolve the further conflict blocks at L23-74, L82-86, and L141-158; keep the --no-wait-ready option and waitUntilServing branch.
  • src/lib/api/oss.ts#L69-L120: keep the HEAD handleBranchProvisioningError (L74-116), drop markers.
  • src/commands/branch/delete.test.ts#L216-L276: keep the HEAD "still busy after max retry time" test, drop the empty incoming side.
  • src/commands/branch/create.test.ts#L33-L57: also resolve the additional blocks at L345-384, L390-391, L690, and L695-846; keep the HEAD readiness/adoption tests.
🧰 Tools
🪛 Biome (2.5.3)

[error] 3-3: Expected a statement but instead found '<<<<<<< HEAD'.

(parse)


[error] 11-11: Expected a statement but instead found '======='.

(parse)


[error] 13-13: Expected a statement but instead found '>>>>>>> 34b302e'.

(parse)


[error] 13-13: numbers cannot be followed by identifiers directly after

(parse)

📍 Affects 4 files
  • src/commands/branch/create.ts#L3-L13 (this comment)
  • src/lib/api/oss.ts#L69-L120
  • src/commands/branch/delete.test.ts#L216-L276
  • src/commands/branch/create.test.ts#L33-L57
🤖 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 `@src/commands/branch/create.ts` around lines 3 - 13, Resolve all remaining
merge conflicts by retaining the HEAD implementation and removing conflict
markers and incoming-side code. In src/commands/branch/create.ts at lines 3-13,
23-74, 82-86, and 141-158, preserve NETWORK_ERROR_CODE, probeBackendHealth,
--no-wait-ready, and the waitUntilServing branch; in src/lib/api/oss.ts lines
69-120, retain handleBranchProvisioningError; in
src/commands/branch/delete.test.ts lines 216-276, retain the “still busy after
max retry time” test; and in src/commands/branch/create.test.ts lines 33-57,
345-384, 390-391, 690, and 695-846, retain the HEAD readiness/adoption tests.
Ensure no conflict markers remain and all files parse and compile.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-issue PR isn't linked to any issue — open and claim an issue first, then link it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants