Skip to content

Fix inconsistencies across the CLI commands and MCP tools - #282

Draft
nathanjcochran wants to merge 26 commits into
mainfrom
nathan/fix-inconsistencies
Draft

nathanjcochran wants to merge 26 commits into
mainfrom
nathan/fix-inconsistencies

Conversation

@nathanjcochran

@nathanjcochran nathanjcochran commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Fixes inconsistencies across the commands and MCP tools, where some followed the conventions in the agent guide and others didn't, and tidies up a few related things.

Behavior

  • service_rename trims the new name and refuses an empty one, as the CLI's tiger service rename does.
  • API responses are checked by shared helpers. common.JSON200/JSON201/JSON202 and common.Status202/Status204 take both return values of a client call and check the request error, the expected status, and the body, replacing the hand-written checks at every call site and the wrappers that only did those checks (common.GetService, common.ListBranches, common.CountBranches). A failed request now reads "failed to call API: …" (the client's error names the method and URL) rather than naming the operation; API errors read as before. The resolve helpers, the wait loop, and API key validation keep their own checks, since they act on particular statuses.
  • Missing response bodies are an error everywhere ("empty response from API"). Before, service_list, service_backup_list, and the metrics tools returned an empty list; tiger project use and the project selection in tiger auth login reported an "unknown error"; tiger db psql treated it as no read replicas; and the wait loop and log fetcher used their own wording. A JSON null list is still an empty list, and tiger service metrics available/series now print it as [] rather than null.
  • Flags are validated first, before the config, client, or any API call. --wait-timeout must be positive on every command that waits, not just tiger service create. tiger service logs rejects a --tail below 1 (a negative one panicked) and a negative --node, as the service_logs tool does. tiger db create role checks its --from tsdbadmin restrictions up front rather than after connecting to the database.
  • Output formats are lowercased before every output switch, and a format a command doesn't support (from TIGER_OUTPUT or the config file, such as env on a list command) falls through to the table rather than erroring on a few commands.
  • tiger project list prints "No projects found." for an empty list, like the other list commands.
  • Group commands report an unknown subcommand as an error, with suggestions (tiger service lsit → "Did you mean this? list"), rather than printing help and exiting 0. Cobra only does this on its own for the root command; only tiger mcp did it before. tiger mcp start, which runs on its own but also has subcommands, gives the same suggestions. With no subcommand they still print their help, without loading the config. Every group command now has tests for this.
  • tiger mcp start http prints errors that come before the server starts (an unknown flag or argument, a config that fails to load), which it silently exited 1 on before. Only the server's own failures, which it logs, are left unprinted.
  • The MCP tools no longer log a line per call. Failures that would otherwise be swallowed are still logged.

Wording

  • Warnings start lowercase (Warning: failed to …), and every TTY-not-detected error reads TTY not detected - cannot prompt for <what>. <The alternative>.
  • Resource names and IDs are single-quoted everywhere, as Accept a service name anywhere a service ID is accepted #241 settled on for the CLI, including the MCP tool messages and elicitation prompts.
  • --output, --no-wait, and --wait-timeout have the same help text on every command (tiger service logs listed a text format it doesn't accept).
  • Every --no-wait hint names the resource's ID (the branch hints suggested tiger branch get with no ID, which can't work).
  • service_fork and service_resize messages match their siblings', service_rename's message no longer says "successfully", and the service_logs output schema describes its timestamp format correctly.
  • Static error strings use errors.New rather than fmt.Errorf.

Repo

  • Renovate's dockerfileVersions custom manager and the Dockerfile's # renovate: comment are gone: the built-in Dockerfile manager already reads the Go version from the ARG default, so they extracted it a second time (confirmed with a local dry run).
  • CLAUDE.md is now AGENTS.md. It also now covers the API response helpers, group commands, where to set SilenceErrors, flag validation order, empty list bodies, lowercasing and falling through on output formats, the TTY error wording, and quoting.

- service_rename trims the new name and refuses an empty one, as the CLI's
  service rename does. Its message names the service and its new name
  instead of saying it was renamed successfully.
- service_list treats a 200 without a body as an error, as the CLI's
  service list does, instead of returning an empty list.
- The warnings in service create, service fork, and the service output's
  connection details start lowercase, like every other warning.
@nathanjcochran nathanjcochran self-assigned this Oct 28, 2026
Only service create rejected a zero or negative --wait-timeout. The other
commands that wait (service fork, start, stop, resize, and branch create,
start, stop, resize) now share the same check, validateWaitTimeout.
A 200 with no body indicates a bug, and most commands and tools already
report it as "empty response from API". The rest now do too:

- The service_backup_list, service_metrics_available, and
  service_metrics_series tools returned an empty list instead.
- tiger project use and the project selection in tiger auth login reported
  it as an "unknown error", since they checked the body rather than the
  status code.
- tiger db psql treated it as a service with no read replicas.
- The wait loop and the log fetcher used their own wording for it.

A JSON null list is still an empty list, and tiger service metrics
available and series now print it as [] rather than null, as the other
list commands do.
CLAUDE.md reserves fmt.Errorf for errors that need format verbs, but about a
hundred static error strings still went through it.
--output rejects formats a command doesn't support, but TIGER_OUTPUT and
the config file aren't validated against the command. Most commands then
fell through to the table, but service list, project list, service backup
list, and service backup region list/add refused env with an error. They
now fall through to the table like the rest.
The same flags were described several different ways across commands. Every
--output now lists its formats as json, yaml, any extra, then table, and
every --no-wait and --wait-timeout shares one description. tiger service
logs listed a text format that --output rejects; it now lists table, which
it accepts.
The service_logs tool's schema requires a tail of at least 1 and a
non-negative node, but the CLI accepted anything: a negative --tail
panicked slicing the fetched entries, --tail 0 printed nothing, and a
negative --node went to the API. They're now rejected up front.
The output schema said each line starts with an RFC3339 timestamp, but the
tool writes "2006-01-02 15:04:05 UTC", and only for entries that have a
timestamp.
The tools' messages follow the same wording rules as the CLI. service_fork
now ends with "Service is ready.", as service_create and both CLI commands
do, and service_resize's accepted message starts "Service resize request
accepted", like service_start's and service_stop's.
With --no-wait, tiger branch start, stop, and resize suggested 'tiger branch
get', which can't work: a branch command has no default to fall back on.
The service commands suggested 'tiger service get' or 'tiger service list',
which only find the service if it's the default or in a short list. Every
hint now names the ID, as tiger branch create's already did, in the same
wording.
Every tool logged a line at Info level for each call, and a few more on
success, though a default tiger mcp start is meant to be silent. Failures
that would otherwise be swallowed are still logged.
It only printed help, which cobra already does for a group command with no
Run, and it was the one command using Run rather than RunE.
Most output switches matched on strings.ToLower(cfg.Output), but version,
db query, db extensions list, config list, and mcp list/get matched it
as given, so TIGER_OUTPUT=JSON printed JSON from some commands and a table
from others.
tiger project list rendered an empty table when there were no projects,
where the other list commands print a message to stderr.
Each prompt that needs a terminal refused without one in its own words. They
now all say what couldn't be prompted for and then how to avoid the prompt.
tiger auth login also mentions --project-id when it can't prompt for a
project, and tiger db psql names tiger db save-password when it can't
prompt for a password.
Most commands checked their flags first, but service resize, branch create,
branch resize, the update-password commands, db query, and db ping loaded
the config and client (and some resolved the service) before checking
flag values that need neither, and db create role only checked its --from
tsdbadmin restrictions after connecting to the database. They now all
check flags first.
#241 settled on single quotes for service names and IDs, keeping %q for
opaque values, but the MCP tool messages and elicitation prompts and a few
shared errors and warnings still used %q. They now single-quote too.
…rsion

Renovate's Dockerfile manager already reads golang:${GO_VERSION} from the
ARG's default, so the dockerfileVersions custom manager and its # renovate:
comment extracted the same dependency a second time. A local dry run finds
golang 1.27 in the Dockerfile with or without them.
The guide is for any coding agent, not one in particular. References to it
in the docs and code comments now point at AGENTS.md, and the development
guide's pointer to a "Unit Test Pattern" section that no longer exists now
points at the Testing section.
@nathanjcochran nathanjcochran changed the title Fix a few inconsistencies in the service commands and tools Fix inconsistencies across the CLI commands and MCP tools Oct 29, 2026
Cobra only reports an unknown subcommand on the root command. A group
command with no Run printed its help instead and exited 0, so a typo like
'tiger service lsit' went unnoticed by scripts, while tiger mcp errored
because it alone had a Run. Every group command now sets Run: groupRun,
which prints its help, and Args: groupArgs, which reports an unknown
subcommand with cobra's suggestions. Run rather than RunE keeps them out of
the per-invocation lifecycle, so printing help still loads nothing.
Each group command gets the same three cases TestServiceCmd has: help with
no subcommand, without loading anything, and an unknown subcommand with
and without suggestions.
It set SilenceErrors on its command struct, since the server logs its own
failures, but that also hid every error before the server started: an
unknown flag or argument, or a config that failed to load, exited 1 with
no output. It now sets SilenceErrors in RunE, just before starting the
server, as the other commands that report their own errors do.
tiger mcp start runs on its own and has subcommands, but used cobra.NoArgs,
so 'tiger mcp start stdoi' didn't suggest stdio as a group command would.
It now uses groupArgs too.
Every API call repeats the same checks after calling the generated client:
the request error, the status code, and a nil body. JSON200, JSON201, and
JSON202 do all three for the status their endpoint returns and hand back
the body; Status202 and Status204 do the first two for calls whose body
isn't needed.
Every command and tool that calls the Tiger Cloud API now checks the
response with common.JSON2xx or common.Status2xx instead of by hand, and the
wrappers that only existed to do those checks (common.GetService,
common.ListBranches, common.CountBranches, and fetchReplicaSets in db psql)
are inlined at their callers. API errors and missing bodies are reported as
before; a failed request now reads "failed to call API: ..." rather than
naming the operation, which the client's error already does with the
method and URL.

The resolve helpers keep their own checks, since they turn a 404 into a
"not found" error and add a hint to a 400, as do the wait loop, which keeps
polling through errors, and API key validation.
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