Skip to content

Report missing databases and roles on delete instead of empty output - #548

Open
andrelandgraf wants to merge 8 commits into
mainfrom
andre/cli-delete-not-found
Open

andrelandgraf wants to merge 8 commits into
mainfrom
andre/cli-delete-not-found

Conversation

@andrelandgraf

@andrelandgraf andrelandgraf commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Problem

neon databases delete <name> and neon roles delete <name> for a name that is already gone print nothing and exit 0. The API answers the DELETE with HTTP 204 and no body, and both commands only write output when a 200 body arrives, so the missing case falls through silently.

A script that deletes and then checks $? sees success. A person at the terminal sees an empty line and has to run databases list to learn whether anything happened.

Diagnosis

On origin/main both delete handlers end in if (data) { writer(props).end(...) }. With a 204 there is no record to print and no else branch, so the command returns normally and the process exits 0.

The fix keys off status rather than the presence of data. Some clients parse an empty 204 body as {}, which is truthy, so status === 200 && data?.database (or data?.role) is the condition for the success path. Everything else is treated as "the target was not there".

Reporting has to differ by output mode because of how the CLI's error path works. Throwing an Error makes the top-level handler print ERROR: <message> on stderr and exit 1, which is the right shape for table mode. In JSON and YAML mode the caller is parsing stdout, so the result goes through the normal writer as an object and process.exitCode is set to 1 instead of throwing. Both handlers share this in packages/cli/src/utils/missing_delete.ts.

The branch in the message is built from values already in hand. If --branch was a name, the message uses that name. If it was a br-… id, or was omitted, the message uses the id that was resolved for the DELETE. There is no second list call after the 204.

Interface

Table mode (default at a terminal): ERROR: on stderr, empty stdout, exit 1.

$ neon databases delete nosuchdb --project-id <id> --branch main
ERROR: Database "nosuchdb" not found on branch main; nothing to delete.
# empty stdout, exit 1

JSON: object on stdout, empty stderr, exit 1.

$ neon databases delete nosuchdb --project-id <id> --branch main --output json
{
  "deleted": false,
  "message": "Database \"nosuchdb\" not found on branch main; nothing to delete."
}
# empty stderr, exit 1

YAML emits the same two keys, deleted: false and message.

roles delete behaves identically with Role in the message.

Which branch label appears in the message:

$ neon databases delete nosuchdb --project-id <id> --branch main
ERROR: Database "nosuchdb" not found on branch main; nothing to delete.

$ neon databases delete nosuchdb --project-id <id> --branch br-abc-123456
ERROR: Database "nosuchdb" not found on branch br-abc-123456; nothing to delete.

$ neon databases delete nosuchdb --project-id <id>
ERROR: Database "nosuchdb" not found on branch br-<default-id>; nothing to delete.

Unchanged for existing callers:

  • A successful delete still resolves the branch with branchIdFromProps, prints the deleted record with the same fields, and exits 0.
  • Help text for both commands stays Delete a database and Delete a role, matching the other delete subcommands.
  • No new flags.

Also in here

  • packages/cli/src/utils/missing_delete.ts: reportMissingDelete (mode-dependent reporting) and branchNameForMissingDelete (name vs id selection using the existing looksLikeBranchId). Both handlers call it so the two nouns cannot drift.
  • Two empty DELETE.204.json mock files under the existing mock branch, for databases/nosuchdb and roles/nosuchrole, so the test fixture serves a 204 for those paths.
  • Changeset: neon minor. Exit code changes from 0 to 1 for a case that used to succeed silently.

Verification

$ pnpm --filter neon test:ci src/commands/databases.test.ts src/commands/roles.test.ts
12 passed

New tests, each run for both databases delete and roles delete:

  • YAML (fixture default): stdout parses to { deleted: false, message }, stderr empty, exit 1
  • table: stderr is exactly ERROR: <message>, stdout empty, exit 1
  • JSON: stdout parses to { deleted: false, message }, stderr empty, exit 1

Live run against a throwaway project: deleting a missing database and a missing role in table and JSON mode produced the outputs shown above and exited 1. The project was deleted afterwards.

Not covered by an automated test: the br-… id and omitted --branch variants of the message. The fixture passes a branch name; the id path is a direct branch of branchNameForMissingDelete and was checked in the live run.

For your attention

  • Exit code changes for an existing case. Anything that deleted a possibly-absent database or role and relied on exit 0 now gets exit 1. That is the point of the change and the reason for the minor changeset.
  • No --if-exists. A flag to opt back into exit 0 for a missing target is a separate decision and is not in this PR.
  • No --yes / confirmation prompt. Out of scope.
  • Other resources are unchanged. Only databases delete and roles delete return 204 for a missing name through this code path; other delete subcommands were not touched.
  • JSON trailing newline is whatever the existing writer emits. Not changed here.

A typo currently looks like a successful delete: exit 0 and no bytes. Databases stay idempotent (exit 0); a missing role is usually a mistake so that command exits 1 after writing the same not-found payload.
roles delete of a missing name now exits 1, which is a behavior change for scripts that treated 204 as success.
@andrelandgraf andrelandgraf changed the title CLI: print not-found message when deleting a missing database or role Print a not-found message when deleting a missing database or role Sep 5, 2026
…help.

JSON consumers of databases delete cannot use the exit code to tell a 204 from a 200, so the payload needs a discriminant. Help now states the db-0 / role-1 contract.
@andrelandgraf andrelandgraf changed the title Print a not-found message when deleting a missing database or role Report missing databases and roles on delete instead of empty output Sep 5, 2026
…arameter.

The db-0 / role-1 contract stays; it now lives in one helper instead of two copied handlers.
A 204 from either delete is not-found: table ERROR on stderr, JSON/YAML
{deleted:false} on stdout, exit 1 for both nouns.
Successful deletes go back to branchIdFromProps, so a br- id does not
list branches before DELETE.
The not-found payload must not depend on listing branches after the
204. Use the name the user passed, or the id already resolved.
Other delete subcommands only name the noun. The new exit-1 behavior
belongs in the PR, not in two help strings that imply the rest differ.
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