Skip to content

feat(cli): add --branch to app remove to scope teardown to one branch#123

Merged
kristof-siket merged 5 commits into
mainfrom
fix/app-remove-branch-scope
Jul 21, 2026
Merged

feat(cli): add --branch to app remove to scope teardown to one branch#123
kristof-siket merged 5 commits into
mainfrom
fix/app-remove-branch-scope

Conversation

@kristof-siket

Copy link
Copy Markdown
Contributor

Why

app remove destroys every deployment in the resolved branch. But it had no --branch flag — it inferred the branch from the local Git branch and, when it couldn't read one, fell back to the production branch (main):

const gitBranch = await readLocalGitBranch(context.runtime.cwd);
if (gitBranch) return { name: gitBranch, ... };
return { name: "main", annotation: "default" };   // silent production fallback

A PR-teardown CI job that fakes a .git/HEAD to convey the PR branch (because there was no other lever) could therefore silently resolve to production and tear down the live app. This is exactly the mechanism behind a production incident on 2026-07-20 where app remove --app build-runner / --app github-webhook stopped the live production deployments.

What

  • Add --branch <name> to app remove, mirroring app deploy. Callers scope the removal to a specific branch instead of relying on implicit Git resolution.
  • Without --branch, the existing inferred resolution is unchanged (no behavior change for current users).

The consuming CI workflow (pdp-control-plane pr-closed.yaml) will pass --branch "$PR_BRANCH" explicitly, so teardown can never resolve to production.

Follow-up (not in this PR)

Consider hardening app remove itself to refuse an inferred (non---branch) production target in non-interactive mode, so the silent main fallback can't tear down production even without a --branch-aware caller. Left out here to avoid changing existing no---branch behavior/tests.

Tests

  • New: remove scopes the app lookup to an explicit --branch (asserts listApps is scoped to the passed branch).
  • Full app-controller suite green (103 passed).

🤖 Generated with Claude Code

app remove destroys every deployment in the resolved branch, but it had
no --branch flag: it inferred the branch from the local Git branch and
fell back to the production branch ("main") when it couldn't read one.
CI teardown that faked a .git/HEAD to convey a PR branch could therefore
silently resolve to production and tear down the live app.

Add an explicit --branch <name> option (matching app deploy) so callers
scope the removal to a specific branch. Without --branch, existing
inferred resolution is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3afe353e-c467-47ea-bbbd-81235abdc354

📥 Commits

Reviewing files that changed from the base of the PR and between add2753 and 5bd6486.

📒 Files selected for processing (5)
  • docs/product/command-spec.md
  • packages/cli/src/controllers/app.ts
  • packages/cli/src/shell/command-meta.ts
  • packages/cli/tests/app-controller.test.ts
  • packages/cli/tests/app.test.ts

Walkthrough

The app remove command now accepts an optional --branch argument and forwards it to runAppRemove. The controller resolves the specified deploy branch, scopes app lookup to that branch, rejects empty branch values, and removes the resolved app. Tests cover branch-scoped lookup, validation, removal, and CLI wiring.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding --branch support to app remove to scope teardown by branch.
Description check ✅ Passed The description is directly related to the changeset and explains the new --branch behavior, motivation, and tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/app-remove-branch-scope
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/app-remove-branch-scope

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@packages/cli/src/controllers/app.ts`:
- Around line 2058-2060: Update the branch selection near resolveDeployBranch to
distinguish an omitted branchName (undefined) from an explicitly supplied empty
string. Reject an empty branch value with the project’s standard validation
error before resolving the branch, while preserving resolution for valid names
and undefined behavior for omitted input.

In `@packages/cli/tests/app-controller.test.ts`:
- Around line 6764-6825: Add a command-level test covering createRemoveCommand
that parses “app remove --branch pr-42” and verifies the invoked controller
receives “pr-42” as the branch argument. Keep the existing runAppRemove test for
provider scoping, but ensure the new test exercises CLI option registration and
forwarding rather than calling the controller directly.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: fe5dbf4f-6aec-4670-8456-17c692f0ad55

📥 Commits

Reviewing files that changed from the base of the PR and between b12c585 and 4550777.

📒 Files selected for processing (3)
  • packages/cli/src/commands/app/index.ts
  • packages/cli/src/controllers/app.ts
  • packages/cli/tests/app-controller.test.ts

Comment thread packages/cli/src/controllers/app.ts Outdated
Comment thread packages/cli/tests/app-controller.test.ts
Address CodeRabbit review on the app remove --branch change:

- Reject an explicitly supplied empty --branch (`--branch ""`) with a
  usage error instead of treating it as omitted. Previously the
  truthiness check let a blank value fall through to inferred Git-branch
  resolution — which can resolve to production — defeating the point of
  scoping the removal.
- Distinguish undefined (omitted) from "" so omitting --branch keeps the
  existing inferred behavior.
- Add command-level coverage: assert `app remove` registers --branch
  (guards against the option/forwarding being dropped) and a controller
  test that the empty value is rejected before any provider call.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kristof-siket

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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

@kristof-siket

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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

…mmand

CodeRabbit follow-up: the registration guard plus the controller test
still left a gap — a regression where --branch stays registered but
options.branch is no longer passed to runAppRemove would pass both.

Add a command-execution test that parses `app remove --branch pr-42`
via createAppCommand and asserts the provider lookup is scoped to that
branch, exercising commander parsing, the action handler's forwarding,
and the controller's branch scoping in one path.

Verified by mutation: dropping branchName from the action call makes the
provider receive branchName "main" (the inferred production fallback)
instead of "pr-42", and the test fails.

Mocks the app provider rather than the controllers module — mocking
controllers/app leaked past vi.resetModules and broke six later tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 21, 2026
Comply with the house rule that a comment needing more than one line
means the code (or its API doc) is what's unclear. The branch-scoping
rationale now lives in runAppRemove's JSDoc, the empty-branch guard
keeps a single-line note, and the test drops a comment its name
already covers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Update the command spec signature, purpose, and behavior for
`app remove` to cover branch scoping, and surface the flag in the
in-CLI help examples. The description now says "resolved branch"
rather than "current branch", since the target branch can be chosen.

The branch-resolution section already covered management commands
accepting `--branch`, so it needed no change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kristof-siket

Copy link
Copy Markdown
Contributor Author

@CodeRabbit approve

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@kristof-siket
kristof-siket merged commit 484c60a into main Jul 21, 2026
10 checks passed
@kristof-siket
kristof-siket deleted the fix/app-remove-branch-scope branch July 21, 2026 07:10
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