Skip to content

Accept a service name anywhere a service ID is accepted - #241

Merged
aprimakina merged 4 commits into
mainfrom
alexandra/service-name-refs
Sep 30, 2026
Merged

aprimakina merged 4 commits into
mainfrom
alexandra/service-name-refs

Conversation

@aprimakina

@aprimakina aprimakina commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Every command that identifies a service now takes a ref — its ID, a read replica set ID, or its name:

tiger db psql my-api-db
tiger service stop my-api-db

The CLI classifies nothing. The resolveServiceRef API operation matches ID and name together and refuses a ref matching more than one service, so nothing here ever tries to tell an ID from a name by its shape.

How it works

  • getServiceRef picks the ref out of args[0] or cfg.ServiceID, recording which it was. The --service-id flag, TIGER_SERVICE_ID and the config file share that second path.
  • resolveService, resolveServiceID and resolveServiceForWrite (new internal/cmd/service_ref_helper.go) hand it to the API. Resolving returns the whole service, so the nine commands that already fetched one pay nothing for resolution.
  • resolveServiceForWrite also carries the read-only gate for the seven commands that change the service they resolve: it refuses the blanket case before the network call, then gates on the tag the resolution returns, so neither half can be skipped or ordered wrong at a call site.

Deliberate choices

  • A name is accepted as an argument, but not as a stored default. An argument's result is visible immediately; --service-id, TIGER_SERVICE_ID and the config file are set once and forgotten, so a name there works until someone renames the service and then breaks every command at once. An ID never changes.

  • Status output names the service, not just its ID. Typing a name and getting an opaque string back is no confirmation you hit the right service, so every status line carries both, through one serviceLabel helper:

    Start request accepted for service 'my-api-db' (nm04gwe7oq).
    
  • Error wording was made consistent while the output was being touched: five failed to … Service capitals lowercased, service is required and service cannot be empty unified as service name or ID is required, and service identifiers single-quoted in errors (with %q kept for opaque values).

  • Destructive commands accept a name; their confirmation prompt does not. service delete shows both forms — 'my-api-db' (kd9w2xp4mz) — and requires the ID to be typed back.

  • Completion keeps offering IDs, with the name as the description.

  • MCP tools stay IDs-only, an intentional CLI/MCP divergence documented at setServiceIDSchemaProperties.

  • The positional is [name-or-id] rather than [service], so the usage line itself shows both accepted forms.

@aprimakina aprimakina self-assigned this Sep 22, 2026
@aprimakina
aprimakina force-pushed the alexandra/service-name-refs branch from 6f2b9d6 to 380538a Compare September 22, 2026 15:07
@aprimakina
aprimakina marked this pull request as ready for review September 23, 2026 09:11
@nathanjcochran
nathanjcochran self-requested a review September 25, 2026 20:37

@nathanjcochran nathanjcochran 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.

Left some comments, but overall this looks great! Thank you for doing this. I think it's going to be a HUGE improvement for UX! 💯

Comment thread internal/cmd/config_set.go Outdated
Comment thread internal/cmd/main_test.go Outdated
Comment thread internal/cmd/service_delete.go Outdated
Comment thread internal/cmd/service_get_test.go
Comment thread internal/cmd/service_ref_helper.go Outdated
Comment thread internal/mcp/utils.go Outdated
Comment thread CLAUDE.md
Comment thread README.md
@aprimakina
aprimakina force-pushed the alexandra/service-name-refs branch from 4136790 to a243961 Compare September 29, 2026 19:43
@aprimakina
aprimakina requested a review from a team as a code owner September 29, 2026 19:43
@aprimakina
aprimakina force-pushed the alexandra/service-name-refs branch 2 times, most recently from 20a21b9 to f5fb479 Compare September 30, 2026 08:15
aprimakina and others added 4 commits September 30, 2026 10:22
Every command that identifies a service now takes a ref: its ID, a read
replica set ID, or its name. The new `resolveServiceRef` API operation
matches ID and name together and refuses a ref matching more than one
service, so the CLI classifies nothing and never has to tell them apart.

- `getServiceRef` picks the ref out of `args[0]` or `cfg.ServiceID`, so the
  `--service-id` flag, `TIGER_SERVICE_ID` and the config file all arrive
  through one path. The positional is `[name-or-id]` in every usage line.
- `resolveService`, `resolveServiceID` and `resolveServiceForWrite`
  (`service_ref_helper.go`) hand it to the API. Resolving returns the whole
  service, so the commands that already fetched one pay nothing for it.
- `resolveServiceForWrite` carries the read-only gate for the commands that
  change the service they resolve: it refuses the blanket case before the
  network call, then gates on the tag the resolution returns, so neither
  half can be skipped or ordered wrong at a call site. The refusal no
  longer prefixes the service, which only ever echoed the argument back;
  `CheckReadOnlyByServiceID` still names it when the tag lookup fails.
- A configured default must be an ID. A name there breaks silently on the
  next rename, so it is refused after the lookup with the ID to store
  instead. `config set service_id` resolves at write time and stores the
  ID, echoing the resolution when a name was given.
- Destructive commands accept a name, but their confirmation prompt shows
  both forms and still takes only the ID.
- Status output names a service as 'name' (id) through one `serviceLabel`
  helper, so someone who typed a name sees which service it hit.
- "failed to <verb> Service" is lowercased, and "service is required" /
  "service cannot be empty" both become "service name or ID is required".
- MCP tools stay IDs-only, an intentional divergence documented at
  `setServiceIDSchemaProperties`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Drop name resolution from `config set service_id`: it stores the value
  as given again, like every other key. A name there is still refused when
  a command reads the default, and the error no longer suggests
  `config set` as the way to turn a name into an ID.
- Drop `resolveServiceID`; callers use `resolveService` and read
  `.ServiceID`.
- Drop the `serviceID := service.ServiceID` aliases.
- Split the variadic `expectResolveRef` into `expectResolveRef(m, ref, svc)`
  and `expectResolveRefID(m, ref)`.
- Test the ambiguous-name refusal in every command that resolves a ref.
- Correct the MCP comment: only the resolve operation takes a name, and
  the tools never call it.
- Trim the service-ref sections of CLAUDE.md and README.md to the rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gateway replaced the resolve operation (POST .../services/resolve)
with an internal ref query parameter on getServices. Sync openapi.yaml
from it and regenerate.

- resolveService calls GetServicesWithResponse with the ref. A match is a
  one-item list; no match is an empty list rather than a 404, so the CLI
  now writes "service '<ref>' not found" itself, still exiting with
  ExitServiceNotFound. More than one item means the filter was ignored,
  and is refused rather than acted on.
- The existing list callers pass nil params.
- The resolve test helpers expect the list call with the ref, and
  expectResolveRefNotFound covers the empty list; the cases that used a
  404 for not-found now use it.
- CLAUDE.md and the MCP comment describe the ref filter.
- The integration test for a deleted service expects the CLI's own
  not-found message.
- The name-as-default error states the rule and the fix without the
  rationale, which stays in the serviceRef comment; two comments that
  repeated it are trimmed.
- service get's cases follow the command's execution order.

The sync also brings the gateway's deprecation of the 'ai' add-on and the
removal of the legacy database metrics series, both description-only here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`service backup region add`, `list` and `remove` arrived on main taking a
service ID only. They now take a ref like every other command:

- `add` and `list` accept a name as an argument, fall back to the default
  service, and resolve through resolveServiceForWrite and resolveService
  respectively.
- `remove` follows `service delete`: the service must be given explicitly,
  a name is accepted, and the confirmation prompt shows both forms but
  takes only the ID. Read-only mode refuses before the prompt.
- Status output names the service by both forms.

Their tests follow the other command tables, including the ambiguous-name
case and delete's prompt cases for `remove`. The MCP region tools stay
IDs-only.

Also: `service delete` shows `<name-or-id>`, as a required argument should,
and the `service backup list` ambiguous case runs the renamed command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aprimakina
aprimakina force-pushed the alexandra/service-name-refs branch from f5fb479 to 626fdbb Compare September 30, 2026 08:35
@aprimakina
aprimakina merged commit dd9273c into main Sep 30, 2026
2 of 3 checks passed
@aprimakina
aprimakina deleted the alexandra/service-name-refs branch September 30, 2026 09:06
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