Skip to content

feat(project): add project create command#237

Open
betegon wants to merge 17 commits intomainfrom
feat/project-create
Open

feat(project): add project create command#237
betegon wants to merge 17 commits intomainfrom
feat/project-create

Conversation

@betegon
Copy link
Member

@betegon betegon commented Feb 12, 2026

Summary

Adds sentry project create <name> <platform> — the first write command in the CLI. Follows gh repo create conventions with org/name syntax, auto-detection of org and team, and actionable errors at every step.

Changes

  • New SentryTeam Zod schema and type
  • listTeams() and createProject() API functions with region-aware routing (TEAM_ENDPOINT_REGEX)
  • project create command with two required positionals, --team/-t flag, and --json output
  • Team auto-selection when org has exactly one team; lists available teams otherwise
  • Org fallback: on 404 from bad auto-detected org, shows user's actual organizations
  • DSN fetched after creation (best-effort) so the user can start sending events immediately
  • 19 tests covering happy path, missing args, team resolution, conflict handling, and JSON output

Test Plan

  • bun test test/commands/project/create.test.ts — 19 pass, 0 fail
  • bun run typecheck — clean
  • bun run lint — clean
  • Manually tested against live API: create with auto-team, explicit team, bad team, 409 conflict, missing args, JSON mode

Add team schema/type and two new API functions needed for project
creation. Also adds TEAM_ENDPOINT_REGEX so /teams/{org}/... endpoints
route to the correct region.
Adds `sentry project create <name> <platform> [--team] [--json]`.

Supports org/name syntax (like gh repo create owner/repo), auto-detects
org from config/DSN, and auto-selects team when the org has exactly one.
Fetches the DSN after creation so users can start sending events
immediately. All error paths are actionable — wrong org lists your orgs,
wrong team lists available teams, 409 links to the existing project.
@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 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 ✨

Build

  • Add hole-punch tool to reduce compressed binary size by BYK in #245
  • Add gzip-compressed binary downloads by BYK in #244

Other

  • (args) Parse Sentry web URLs as CLI arguments by BYK in #252
  • (list) Add pagination and consistent target parsing to all list commands by BYK in #262
  • (project) Add project create command by betegon in #237

Bug Fixes 🐛

Telemetry

  • Reduce noise from version-check JSON parse errors by BYK in #253
  • Skip Sentry reporting for 4xx API errors by BYK in #251
  • Handle EPIPE errors from piped stdout gracefully by BYK in #250
  • Upgrade Sentry SDK to 10.39.0 and remove custom patches by BYK in #249

Other

  • (commands) Support org/project/id as single positional arg by BYK in #261
  • (db) Handle readonly database gracefully instead of crashing by betegon in #235
  • (errors) Show meaningful detail instead of [object Object] in API errors by BYK in #259
  • (issue-list) Propagate original errors instead of wrapping in plain Error by BYK in #254
  • (polyfill) Add exited promise and stdin to Bun.spawn Node.js polyfill by BYK in #248
  • (project-list) Add pagination and flexible target parsing by BYK in #221
  • (test) Prevent mock.module() leak from breaking test:isolated by BYK in #260
  • (upgrade) Remove v prefix from release URLs and work around Bun.write streaming bug by BYK in #243
  • Repair pagination_cursors composite PK and isolate test suites by BYK in #265

Internal Changes 🔧

  • (build) Replace local hole-punch script with binpunch package by BYK in #246
  • Use @sentry/api client for requests by MathurAditya724 in #226

🤖 This preview updates automatically when you update the PR.

github-actions bot and others added 4 commits February 12, 2026 20:14
When the API returns 400 for an invalid platform string, show the same
helpful platform list instead of a raw JSON error body.
Replace the confusing 'Or: - Available platforms:' pattern with a
cleaner 'Usage: ... Available platforms:' layout. Applies to both
missing platform and invalid platform errors.
- tryGetPrimaryDsn() → api-client.ts (was duplicated in view + create)
- resolveTeam() → resolve-team.ts (reusable for future team-dependent commands)
- parseOrgPrefixedArg() → arg-parsing.ts (reusable org/name parsing)
- writeKeyValue() for aligned key-value output in create.ts
- project/view.ts now uses shared tryGetPrimaryDsn instead of local copy
@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

Codecov Results 📊

✅ Patch coverage is 93.21%. Project has 3669 uncovered lines.
✅ Project coverage is 74.55%. Comparing base (base) to head (head).

Files with missing lines (71)
File Patch % Lines
human.ts 58.39% ⚠️ 394 Missing
resolve-target.ts 37.26% ⚠️ 325 Missing
api-client.ts 69.63% ⚠️ 253 Missing
oauth.ts 30.94% ⚠️ 183 Missing
list.ts 29.55% ⚠️ 155 Missing
plan.ts 19.37% ⚠️ 154 Missing
list.ts 72.41% ⚠️ 120 Missing
resolver.ts 3.23% ⚠️ 120 Missing
help.ts 19.85% ⚠️ 109 Missing
upgrade.ts 61.37% ⚠️ 107 Missing
view.ts 41.90% ⚠️ 104 Missing
interactive-login.ts 9.17% ⚠️ 99 Missing
errors.ts 5.94% ⚠️ 95 Missing
view.ts 25.81% ⚠️ 92 Missing
view.ts 41.50% ⚠️ 86 Missing
clipboard.ts 4.49% ⚠️ 85 Missing
status.ts 24.07% ⚠️ 82 Missing
migration.ts 47.44% ⚠️ 82 Missing
list.ts 27.18% ⚠️ 75 Missing
browser.ts 4.11% ⚠️ 70 Missing
login.ts 33.33% ⚠️ 64 Missing
list.ts 86.08% ⚠️ 59 Missing
span-tree.ts 5.00% ⚠️ 57 Missing
explain.ts 33.33% ⚠️ 56 Missing
api.ts 89.80% ⚠️ 47 Missing
upgrade.ts 66.91% ⚠️ 46 Missing
seer.ts 75.54% ⚠️ 45 Missing
schema.ts 91.51% ⚠️ 40 Missing
refresh.ts 40.63% ⚠️ 38 Missing
seer.ts 79.87% ⚠️ 30 Missing
preload.ts 53.23% ⚠️ 29 Missing
telemetry.ts 93.01% ⚠️ 27 Missing
utils.ts 88.94% ⚠️ 25 Missing
view.ts 61.54% ⚠️ 25 Missing
view.ts 87.86% ⚠️ 25 Missing
fix.ts 89.43% ⚠️ 24 Missing
org-list.ts 93.81% ⚠️ 21 Missing
detector.ts 90.10% ⚠️ 20 Missing
arg-parsing.ts 91.47% ⚠️ 18 Missing
binary.ts 88.67% ⚠️ 17 Missing
help.ts 57.14% ⚠️ 15 Missing
sentry-client.ts 92.17% ⚠️ 13 Missing
dsn-cache.ts 94.62% ⚠️ 12 Missing
code-scanner.ts 96.25% ⚠️ 12 Missing
logout.ts 56.00% ⚠️ 11 Missing
token.ts 52.17% ⚠️ 11 Missing
qrcode.ts 33.33% ⚠️ 10 Missing
fs-utils.ts 57.14% ⚠️ 9 Missing
view.ts 94.89% ⚠️ 7 Missing
project-root.ts 97.73% ⚠️ 7 Missing
version-check.ts 92.47% ⚠️ 7 Missing
feedback.ts 84.21% ⚠️ 6 Missing
create.ts 97.50% ⚠️ 6 Missing
auth.ts 95.56% ⚠️ 6 Missing
shell.ts 96.23% ⚠️ 6 Missing
app.ts 93.90% ⚠️ 5 Missing
setup.ts 97.84% ⚠️ 4 Missing
resolve-team.ts 96.10% ⚠️ 3 Missing
project-aliases.ts 97.40% ⚠️ 2 Missing
project-root-cache.ts 96.92% ⚠️ 2 Missing
output.ts 89.47% ⚠️ 2 Missing
list.ts 99.01% ⚠️ 1 Missing
alias.ts 99.42% ⚠️ 1 Missing
completions.ts 99.37% ⚠️ 1 Missing
index.ts 98.99% ⚠️ 1 Missing
env-file.ts 99.19% ⚠️ 1 Missing
parser.ts 98.63% ⚠️ 1 Missing
colors.ts 98.21% ⚠️ 1 Missing
trace.ts 99.16% ⚠️ 1 Missing
region.ts 97.30% ⚠️ 1 Missing
helpers.ts 94.74% ⚠️ 1 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    74.07%    74.55%    +0.48%
==========================================
  Files          114       116        +2
  Lines        14065     14415      +350
  Branches         0         0         —
==========================================
+ Hits         10418     10746      +328
- Misses        3647      3669       +22
- Partials         0         0         —

Generated by Codecov Action

# Conflicts:
#	src/lib/api-client.ts
The /teams/{org}/{team}/projects/ endpoint returns 404 for both a bad
org and a bad team. Previously we always blamed the team, which was
misleading when --team was explicit and the org was auto-detected wrong.

Now on 404 we call listTeams(orgSlug) to check:
- If it succeeds → team is wrong, show available teams
- If it fails → org is wrong, show user's actual organizations

Only adds an API call on the error path, never on the happy path.
The view command hint on 409 used the raw name ('My Cool App') instead
of the expected slug ('my-cool-app'), pointing to a non-existent target.
handleCreateProject404 was treating any listTeams failure as proof that
the org doesn't exist. Now it checks the status code: only 404 triggers
'Organization not found'. Other failures (403, 5xx, network) get a
generic message that doesn't misdiagnose the root cause.
Same class of bug as the previous fix in handleCreateProject404:
resolveTeam was routing all ApiErrors from listTeams into the 'org not
found' path. Now only 404 triggers that diagnosis. Other failures
(403, 5xx) get a generic message that doesn't misdiagnose the cause.
@betegon betegon requested a review from BYK February 13, 2026 19:46
# Conflicts:
#	plugins/sentry-cli/skills/sentry-cli/SKILL.md
#	src/lib/api-client.ts
#	src/lib/arg-parsing.ts
#	src/types/sentry.ts
- Extract shared fetchOrgListHint() in resolve-team.ts to deduplicate
  org-list fetching logic (used by both resolve-team and create 404 handler)
- Use Writer type instead of inline { write } in writeKeyValue
- Simplify Awaited<ReturnType<typeof listTeams>> to SentryTeam[]
- Add fragility comment to isPlatformError (relies on API message wording)
- Fix test import to use barrel (types/index.js)
When a project slug is already taken, Sentry silently appends a random
suffix (e.g., 'test1' becomes 'test1-0g'). This was confusing because
the user had no indication why the slug differed from the name.

Now shows: Note: Slug 'test1-0g' was assigned because 'test1' is already taken.
Copy link
Member

@BYK BYK left a comment

Choose a reason for hiding this comment

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

Love that you both try to infer team and org automatically while supporting org/proj --team <team> syntax too.

See my comments and decide whether you wanna merge as it is or not. My 2 major concerns are the refactor (I may merge that before you see this) and the error when we find more than 1 team.

* parseOrgPrefixedArg("acme/my-app", "Project name", "sentry project create <org>/<name>")
* // { org: "acme", name: "my-app" }
*/
export function parseOrgPrefixedArg(
Copy link
Member

Choose a reason for hiding this comment

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

I'm doing a massive refactor which may make this part obsolete.

* @returns Team slug to use
* @throws {ContextError} When team cannot be resolved
*/
export async function resolveTeam(
Copy link
Member

Choose a reason for hiding this comment

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

Same as above

});
});

test("errors when multiple teams exist without --team", async () => {
Copy link
Member

Choose a reason for hiding this comment

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

I think we select some team automatically based on the teams the user is a member of or something instead of requiring a manual selection. I wonder if we can do this here too. Otherwise we cannot use project create without passing an explicit --team flag in any non-trivial org as they will 100% have 1+ teams

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.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

"Try:\n" +
` sentry project create ${orgSlug}/${name} ${platform} --team <team-slug>`
);
}
Copy link

Choose a reason for hiding this comment

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

Contradictory "team not found" error when team exists

Low Severity

handleCreateProject404 assumes that when listTeams succeeds, the teamSlug must be wrong, but never checks if teamSlug is actually in the returned list. When the team was auto-selected by resolveTeam (single team in org), listTeams will return that same team, producing a contradictory message like "Team 'engineering' not found" while listing "engineering" as an available team. The function needs to check whether teamSlug is present in the returned teams before claiming it's not found.

Additional Locations (1)

Fix in Cursor Fix in Web

Previously, project create errored whenever an org had 2+ teams,
requiring --team in every non-trivial org. Now filters teams by
isMember and auto-selects when the user belongs to exactly one team.

When multiple member teams exist, only those are shown in the error
(not all org teams). Falls back to the full list when isMember data
is unavailable (self-hosted, old API).
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.

2 participants

Comments