Skip to content

feat: add project delete command#397

Open
MathurAditya724 wants to merge 1 commit intomainfrom
feat/adi/project-delete
Open

feat: add project delete command#397
MathurAditya724 wants to merge 1 commit intomainfrom
feat/adi/project-delete

Conversation

@MathurAditya724
Copy link
Member

@MathurAditya724 MathurAditya724 commented Mar 11, 2026

Summary

Add sentry project delete subcommand for permanently deleting Sentry projects via the API.

Changes

  • src/commands/project/delete.ts — New command with safety measures:
    • Requires explicit <org>/<project> or <project> target (no auto-detect)
    • Interactive confirmation prompt (strict confirmed !== true check for Symbol(clack:cancel) gotcha)
    • --yes/-y flag to skip confirmation for CI/agent usage
    • --dry-run/-n flag to validate inputs and show what would be deleted without deleting
    • Refuses to run in non-interactive mode without --yes
    • Verifies project exists via getProject() before prompting
    • 403 errors throw ApiError with actionable message (preserves HTTP status for upstream handlers)
  • src/commands/project/index.ts — Registered delete in project route map
  • src/lib/api-client.ts — Added deleteProject() using @sentry/api SDK's deleteAProject
  • src/lib/oauth.ts — Added project:admin to OAuth scopes (required for project deletion; existing users must re-run sentry auth login)
  • test/commands/project/delete.test.ts — 10 unit tests covering happy path, error cases, dry-run, JSON output, and safety checks

Usage

sentry project delete acme-corp/my-app
sentry project delete my-app
sentry project delete acme-corp/my-app --yes
sentry project delete acme-corp/my-app --json --yes
sentry project delete acme-corp/my-app --dry-run

Notes

  • Existing tokens do not include project:admin — users need to re-authenticate via sentry auth login after upgrading
  • Local caches (DSN cache, project cache, defaults) are not cleared after deletion — stale entries may persist until TTL expiry (follow-up)

@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (dashboard) Add dashboard list, view, and create commands by betegon in #406
  • Add project delete command by MathurAditya724 in #397
  • Add sentry schema command for API introspection by BYK in #437

Bug Fixes 🐛

  • (dsn) Prevent hang during DSN auto-detection in repos with test fixtures by BYK in #445
  • (formatters) Pad priority labels for consistent TRIAGE column alignment by MathurAditya724 in #449
  • (upgrade) Remove hard chain depth cap for nightly delta upgrades by BYK in #444

Internal Changes 🔧

  • Cache org listing in listOrganizations + DSN shortcut for issue view by betegon in #446

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 11, 2026

Codecov Results 📊

116 passed | Total: 116 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +5
Passed Tests 📈 +5
Failed Tests
Skipped Tests

All tests are passing successfully.

✅ Patch coverage is 92.82%. Project has 1122 uncovered lines.
❌ Project coverage is 95.23%. Comparing base (base) to head (head).

Files with missing lines (4)
File Patch % Lines
oauth.ts 31.70% ⚠️ 181 Missing
human.ts 96.33% ⚠️ 46 Missing
projects.ts 92.31% ⚠️ 15 Missing
delete.ts 99.39% ⚠️ 1 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
- Coverage    95.25%    95.23%    -0.02%
==========================================
  Files          174       175        +1
  Lines        23342     23537      +195
  Branches         0         0         —
==========================================
+ Hits         22234     22415      +181
- Misses        1108      1122       +14
- Partials         0         0         —

Generated by Codecov Action

@MathurAditya724 MathurAditya724 marked this pull request as ready for review March 11, 2026 19:35
@MathurAditya724 MathurAditya724 requested review from BYK and betegon and removed request for BYK March 11, 2026 19:35
const SCOPES = [
"project:read",
"project:write",
"project:admin",
Copy link
Member

Choose a reason for hiding this comment

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

what would happen if someone doesn't have this permission? would it face trouble signing in?

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: New OAuth scope may break login for non-admin users
    • Removed project:admin from global OAuth SCOPES to prevent potentially breaking authentication for non-admin users, and updated the delete command's 403 error to direct users to contact their org admin instead.

Create PR

Or push these changes by commenting:

@cursor push 3eb69e6f5d
Preview (3eb69e6f5d)
diff --git a/src/commands/project/delete.ts b/src/commands/project/delete.ts
--- a/src/commands/project/delete.ts
+++ b/src/commands/project/delete.ts
@@ -176,8 +176,8 @@
       if (error instanceof ApiError && error.status === 403) {
         throw new ApiError(
           `Permission denied: You don't have permission to delete '${orgSlug}/${project.slug}'.\n\n` +
-            "Project deletion requires the 'project:admin' scope.\n" +
-            "  Re-authenticate:  sentry auth login",
+            "Project deletion requires the 'project:admin' permission.\n" +
+            "Contact your organization admin to grant you project admin access.",
           403,
           error.detail,
           error.endpoint

diff --git a/src/lib/oauth.ts b/src/lib/oauth.ts
--- a/src/lib/oauth.ts
+++ b/src/lib/oauth.ts
@@ -52,7 +52,6 @@
 const SCOPES = [
   "project:read",
   "project:write",
-  "project:admin",
   "org:read",
   "event:read",
   "event:write",

diff --git a/test/commands/project/delete.test.ts b/test/commands/project/delete.test.ts
--- a/test/commands/project/delete.test.ts
+++ b/test/commands/project/delete.test.ts
@@ -185,7 +185,7 @@
       const apiErr = error as ApiError;
       expect(apiErr.status).toBe(403);
       expect(apiErr.message).toContain("project:admin");
-      expect(apiErr.message).toContain("sentry auth login");
+      expect(apiErr.message).toContain("organization admin");
     }
   });

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Delete a project

**Flags:**
- `-y, --yes - Skip confirmation prompt`
Copy link
Member

Choose a reason for hiding this comment

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

We should also accept -f/--force flag

Comment on lines +63 to +70
const confirmed = await log.prompt(
`Delete project '${project.name}' (${orgSlug}/${project.slug})? This cannot be undone.`,
{ type: "confirm", initial: false }
);

// consola prompt returns Symbol(clack:cancel) on Ctrl+C — a truthy value.
// Strictly check for `true` to avoid deleting on cancel.
return confirmed === true;
Copy link
Member

Choose a reason for hiding this comment

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

Let's follow the classical safety measure and ask the user to type out org/project like deletions on GitHub UI and other places?


let orgRole: string | undefined;
try {
const org = await getOrganization(orgSlug);
Copy link
Member

Choose a reason for hiding this comment

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

If there's a cached version of that, let's make sure to use that

Comment on lines +109 to +110
"Your auth token may be missing the 'project:admin' scope.\n" +
" Re-authenticate: sentry auth login",
Copy link
Member

Choose a reason for hiding this comment

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

How would this grant that permission? This can only be the case when they are using a custom oauth token so sentry auth login would not help here.

return new ApiError(
`Permission denied: You don't have permission to delete ${label}.\n\n` +
`This requires ${rolesWithAccess} role, or a token with the 'project:admin' scope.\n` +
` Check your role: sentry org view ${orgSlug}\n` +
Copy link
Member

Choose a reason for hiding this comment

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

Does this command really show the user's role in the org? If not let's make that the case

`Permission denied: You don't have permission to delete ${label}.\n\n` +
`This requires ${rolesWithAccess} role, or a token with the 'project:admin' scope.\n` +
` Check your role: sentry org view ${orgSlug}\n` +
" Re-authenticate: sentry auth login",
Copy link
Member

Choose a reason for hiding this comment

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

Reauth using sentry auth login will not resolve any permission issues at all. This could only help when using a custom OAuth token which then they need to regenerate or expand the scope of.

" sentry project delete acme-corp/my-app --dry-run",
},
output: {
json: true,
Copy link
Member

Choose a reason for hiding this comment

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

No longer needed (the default)

Add `sentry project delete` subcommand for permanently deleting
Sentry projects via the API.

Safety measures:
- No auto-detect mode — requires explicit org/project target
- Type-out confirmation (user types `org/project` like GitHub)
- --yes/-y and --force/-f flags to skip confirmation (for CI/agents)
- --dry-run/-n to validate without deleting
- Non-interactive TTY guard (refuses without --yes/--force)
- Role-aware 403 error messages (never suggests reauth)
- Verifies project exists before prompting

Also adds:
- `project:admin` to OAuth SCOPES (required for deletion API)
- `orgRole` display in `sentry org view` output
- `deleteProject()` in API layer

Addresses all review comments from PR #397:
1. Accept -f/--force alongside --yes
2. Type-out org/project confirmation instead of yes/no
3. Note about cached org data (no cache exists yet)
4. Fix misleading 403 token scope messages
5. Show user role in org view
6. Remove sentry auth login suggestions from 403 errors
7. Remove obsolete json: true from output config

Co-authored-by: MathurAditya724 <AditMathur16@gmail.com>
@BYK BYK force-pushed the feat/adi/project-delete branch from 658fcd5 to a4e5715 Compare March 17, 2026 20:00
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Redundant --yes and --force flags with identical behavior
    • Removed the redundant --force flag and consolidated to a single --yes flag while keeping both -y and -f as short aliases for backward compatibility.

Create PR

Or push these changes by commenting:

@cursor push f335ba1cf4
Preview (f335ba1cf4)
diff --git a/plugins/sentry-cli/skills/sentry-cli/SKILL.md b/plugins/sentry-cli/skills/sentry-cli/SKILL.md
--- a/plugins/sentry-cli/skills/sentry-cli/SKILL.md
+++ b/plugins/sentry-cli/skills/sentry-cli/SKILL.md
@@ -186,7 +186,6 @@
 
 **Flags:**
 - `-y, --yes - Skip confirmation prompt`
-- `-f, --force - Force deletion without confirmation`
 - `-n, --dry-run - Validate and show what would be deleted without deleting`
 - `--json - Output as JSON`
 - `--fields <value> - Comma-separated fields to include in JSON output (dot.notation supported)`

diff --git a/src/commands/project/delete.ts b/src/commands/project/delete.ts
--- a/src/commands/project/delete.ts
+++ b/src/commands/project/delete.ts
@@ -64,7 +64,7 @@
   if (!isatty(0)) {
     throw new CliError(
       `Refusing to delete '${expected}' in non-interactive mode. ` +
-        "Use --yes or --force to confirm."
+        "Use --yes to confirm."
     );
   }
 
@@ -154,7 +154,6 @@
 
 type DeleteFlags = {
   readonly yes: boolean;
-  readonly force: boolean;
   readonly "dry-run": boolean;
   readonly json: boolean;
   readonly fields?: string[];
@@ -170,7 +169,6 @@
       "  sentry project delete acme-corp/my-app\n" +
       "  sentry project delete my-app\n" +
       "  sentry project delete acme-corp/my-app --yes\n" +
-      "  sentry project delete acme-corp/my-app --force\n" +
       "  sentry project delete acme-corp/my-app --dry-run",
   },
   output: {
@@ -209,18 +207,13 @@
         brief: "Skip confirmation prompt",
         default: false,
       },
-      force: {
-        kind: "boolean",
-        brief: "Force deletion without confirmation",
-        default: false,
-      },
       "dry-run": {
         kind: "boolean",
         brief: "Validate and show what would be deleted without deleting",
         default: false,
       },
     },
-    aliases: { y: "yes", f: "force", n: "dry-run" },
+    aliases: { y: "yes", f: "yes", n: "dry-run" },
   },
   async *func(this: SentryContext, flags: DeleteFlags, target: string) {
     const { cwd } = this;
@@ -250,7 +243,7 @@
     }
 
     // Confirmation gate
-    if (!(flags.yes || flags.force)) {
+    if (!flags.yes) {
       const confirmed = await confirmDeletion(orgSlug, project);
       if (!confirmed) {
         log.info("Cancelled.");

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

kind: "boolean",
brief: "Force deletion without confirmation",
default: false,
},
Copy link

Choose a reason for hiding this comment

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

Redundant --yes and --force flags with identical behavior

Low Severity

Both --yes and --force flags do exactly the same thing: skip the confirmation prompt. The confirmation gate at if (!(flags.yes || flags.force)) treats them as fully interchangeable, yet they're documented with different descriptions ("Skip confirmation prompt" vs "Force deletion without confirmation") and registered as separate flags. A PR reviewer noted one is "no longer needed (the default)". Having two flags with distinct names but identical runtime behavior adds confusion for users and maintenance overhead.

Additional Locations (1)
Fix in Cursor Fix in Web

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.

3 participants