Freeze the public contract and cut 1.0.0 - #28
Merged
Conversation
Incident comments gained publishOnStatusPage, so incidents comments create and update now generate a --publish-on-status-page flag.
Both write the same request body path and --set was applied last, so --name X --set friendlyName=Y silently discarded the typed flag and created the monitor as Y. Typed flags and --set are peers, so a conflict is now INVALID_INPUT naming the field and both values. Identical values still pass, and --body remains a base document that both may override.
--version is handled inside oclif before any command runs and prints a single human line, so a CI job asserting a minimum version had to parse a sentence. The version command reports the same facts through the normal output flags.
flush() never settles once stdout is an EPIPE'd pipe, so piping to head, head -c, or grep -q printed an unsettled top-level await warning on stderr and exited 13. The broken pipe is now the end of output, keeping any exit code the command had already decided on.
monitors bulk pause, start, and update report success for every matched monitor immediately, but the change takes roughly 40-60 seconds to apply and nothing in the response distinguishes queued from applied. A pipeline that pauses monitors then deploys can page on-call for its own deploy. Descriptions are curated at runtime because the generated ones are guarded by openapi:check.
The code field reported whatever the API supplied and fell back to HTTP_<status> only when there was none, so one 404 was 000-004 and another HTTP_404, and the spelling could change without a CLI release. Those codes appear nowhere in the contract, so code is now always HTTP_<status> and the upstream value rides along verbatim as apiCode, for diagnostics only. Exit codes follow the same status: 400 and 422 map to 2 alongside locally rejected input, and 409 takes 8, so a script can tell a request worth changing from a failure worth retrying.
POST /integrations is a bare oneOf. Adding a discriminator would generate integrations create slack|webhook|... and, as with monitors create, remove the bare command — a breaking change to the command tree that a routine openapi:update would apply silently, since check-openapi only compares generated output to the snapshot. Both paths are expected to coexist.
A heartbeat monitor is inert until something pings its URL, and that URL was obtainable only from the dashboard: the API returns the ingredients without naming the result, with url holding the token and the endpoint composed as <host>/m<id>-<token>. Normalized output now carries pingUrl, leaving --raw untouched, with UPTIMEROBOT_HEARTBEAT_URL to override the host. Derived rather than authoritative — UPT-3726 asks the API to return the URL itself, and this should give way to that field.
UPT-3557 was closed on 2026-08-05 by invalidating public monitor caches after bulk pause/start/update, so the 40-60 seconds this text quoted was a stale read rather than a slow write, and quoting it now tells pipelines to wait for a delay that no longer exists. What survives the fix is structural: the response reports acceptance, carries no operation id, and cannot distinguish queued from applied. That is what the help now says, with no duration attached.
url carries the token a heartbeat's ping endpoint is built from, so anyone holding it — or the pingUrl composed from it — can report the heartbeat and keep a dead service looking alive, with no alert raised and nothing on the monitor to show for it. A single monitors list put every heartbeat token on the account into CI logs and agent transcripts. Redaction is name-based and deliberately shape-agnostic, but url is a plain target address on every other monitor type, so this is keyed on the record's type rather than added to the sensitive-name list. --reveal-secrets shows both and --raw stays the untouched response.
PATCH /integrations/{id} carries the same bare oneOf as the create operation,
12 variants and no discriminator, so a discriminator added to one is the
natural companion change to the other and would topic-ify both. The guard
covered only create.
Minor rather than patch: the error envelope gained apiCode and changed how code is derived, HTTP 400/422/409 map to different exit codes, and a heartbeat monitor's url is now redacted. Each is a changed output shape or exit code, which is a minor bump while 0.x.
WalkthroughThe 1.0.0 release adds a version command, standardized API errors, heartbeat URL derivation and redaction, typed flag conflict validation, bulk-operation guidance, comment publication fields, and graceful broken-pipe handling. ChangesCLI 1.0.0 release
Estimated code review effort: 3 (Moderate) | ~30 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 48-54: Update the changelog entry describing exit code 1 so it
does not claim the code is exclusively for retryable transient faults. Scope
that statement to unmapped HTTP failures or describe it as the generic fallback,
while preserving the documented BULK_FAILED all-failed bulk behavior.
In `@src/lib/request.ts`:
- Around line 171-176: Update sameValue in src/lib/request.ts to compare objects
structurally or through canonicalized, recursively sorted keys so equivalent
JSON values match regardless of property order; preserve existing handling for
primitives, nullish values, and arrays. Add a regression case in
test/set-conflicts.test.ts confirming matching differently ordered object values
are allowed.
In `@src/lib/response-curation.ts`:
- Around line 20-27: Update withDerivedFields so plain records with an existing
string pingUrl are returned unchanged before deriving heartbeatPingUrl; continue
deriving the field only when no authoritative string value is present, and add a
regression case in heartbeat-ping-url.test.ts.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: bc85d692-b6f2-44c9-a7bc-5ab38b735dca
⛔ Files ignored due to path filters (1)
src/generated/operations.tsis excluded by!**/generated/**
📒 Files selected for processing (25)
CHANGELOG.mdREADME.mdbin/run.jsopenapi/openapi.yamlpackage.jsonsrc/api/errors.tssrc/commands/version.tssrc/lib/command-suggestions.tssrc/lib/help-curation.tssrc/lib/operation-command.tssrc/lib/redact-secrets.tssrc/lib/request.tssrc/lib/response-curation.tssrc/lib/types.tstest/api-errors.test.tstest/api-operations.test.tstest/broken-pipe.test.tstest/command-tree-guards.test.tstest/heartbeat-ping-url.test.tstest/heartbeat-secrets.test.tstest/monitors-bulk.test.tstest/monitors-list.test.tstest/set-conflicts.test.tstest/topic-help.test.tstest/version.test.ts
…ison An API-supplied pingUrl was overwritten by the derived one, so the field this composition is meant to defer to could never take precedence. It now wins. sameValue compared serialised JSON, so a typed flag and --set carrying the same object with its keys in a different order were reported as conflicting — a false positive on exactly the case the check is meant to let through. Comparison is now structural. Also scopes the changelog's exit-code claim: 1 remains the generic fallback, including BULK_FAILED, rather than meaning retryable-only.
Cuts the first stable release rather than another 0.x: the contract items from the readiness audit have all landed, so the command tree, output shapes, error codes, exit codes, and environment variables are ready to be held to. The changelog entry states that freeze explicitly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cuts 1.0.0, closing out the readiness audit run against 0.3.0.
Every item here was chosen by one criterion: would deferring it cost a major
version? The CLI's contract — command tree and flags, machine-readable output
shapes, error codes, exit codes, environment variables — is frozen by this release,
so these are the changes that had to land first. The changelog entry states that
freeze explicitly.
The audit's remaining twenty items are gaps, not defects, and every one is additive
— verified rather than assumed — so they can land in 1.x without a major bump.
Contract changes
Error codes are derived from the HTTP status.
codepreviously reported whateverthe API supplied and fell back to
HTTP_<status>only when there was none — so one 404surfaced as
000-004and another asHTTP_404, and which you got depended on theendpoint. Those codes appear nowhere in the OpenAPI contract, and the spelling could
change without a CLI release.
codeis now alwaysHTTP_<status>, and the upstreamvalue rides along verbatim as
apiCode, documented as diagnostic-only.This also keeps the door open: if the API ever documents its codes (UPT-3730), they can
be surfaced as a new field with no breaking change.
Exit codes distinguish "don't retry" from "retrying may help". HTTP 400 and 422 now
exit
2, the code locally-caught invalid input already used. HTTP 409 takes a new8.All three were previously the generic
1, which remains the fallback for everythingelse, including
BULK_FAILED.A typed flag and
--setthat disagree now error.--name X --set friendlyName=Ysilently discarded the typed flag and created the monitor as
Y. They are peers, so theconflict is now
INVALID_INPUTnaming both values.--bodyis unchanged — it stays abase document both may override.
A heartbeat monitor's
urlis redacted. It carries the token its ping endpoint isbuilt from, so anyone holding it can report the heartbeat and keep a dead service looking
alive, with no alert raised. A single
monitors listpreviously put every heartbeattoken on the account into CI logs and agent transcripts. Redaction is keyed on the
monitor's type rather than the field name, so
urlon every other type still prints asthe target address it is.
Additions
uptimerobot versionreports version, platform and Node through the normal outputflags, so CI can assert a minimum version without parsing a sentence. The bare
--versionis handled inside oclif before any command runs and keeps its single humanline.
A derived
pingUrlon heartbeat monitors. The API returns the ingredients withoutever naming the result, so the URL a heartbeat must receive could only be obtained from
the dashboard — cron monitoring, the use case most native to a CLI, could not be
automated end to end.
UPTIMEROBOT_HEARTBEAT_URLoverrides the host. Explicitly derivedrather than authoritative: UPT-3726 asks the API to return it, an API-supplied value
takes precedence, and this gives way when the field exists.
--publish-on-status-pageon the twoincidents commentswrite commands, generatedfrom a field the published contract gained.
Fixes and docs
head,head -corgrep -qno longer printsDetected unsettled top-level awaitto stderr and exits 13.flush()never settles once stdout is anEPIPE'd pipe; the broken pipe is now the end of output, keeping any exit code the
command had already decided on.
monitors bulkcommands document that they report acceptance, notcompletion — no operation id, nothing distinguishing queued from applied. An earlier
draft quoted a 40–60 second delay; UPT-3557 turned out to be a stale cache and was
fixed on 2026-08-05, so the duration is gone and only the structural part remains.
Two regression guards
test/command-tree-guards.test.tsassertsintegrations createandintegrations updatestay invokable as commands. Both are bareoneOfs; adding adiscriminator(UPT-3731) would generate
integrations create slack|webhook|…and, exactly as withmonitors create, remove the bare command. That is additive at the HTTP level butbreaking for the CLI, and
openapi:checkwould not catch it — it compares generatedoutput to the snapshot, and a routine
openapi:updatemoves both together. Adding thesubcommands is welcome; both paths are expected to coexist, with
--setas the untypedescape hatch.
Review
Three CodeRabbit findings, all reproduced and all fixed in d4dd89f:
pingUrloverwrote an API-supplied one, so the authoritative field thiscomposition is meant to defer to could never take precedence. It now wins.
sameValuecompared serialised JSON, so a typed flag and--setcarrying the sameobject with keys in a different order were reported as conflicting — a false positive
on exactly the case the check is meant to allow. Comparison is now structural.
1means retryable-only; it remains the genericfallback, including
BULK_FAILED.Verification
pnpm test271 passing across 33 files, up from 230.lint,typecheck,oxfmt --checkandopenapi:checkall clean.npm pack --dry-runcarries no.d.ts,source maps,
test/orresearch/— 118 files, 92.7 kB.Everything was verified against local stub servers or
--dry-run; there were nocredentials in this session, so nothing was exercised against the live API.
Filed with the API team
UPT-3726 (heartbeat ping URL) · UPT-3727 (
limiton 2 of 12 list endpoints) ·UPT-3728 (announcements cannot be deleted) · UPT-3729 (incidents cannot be filtered to
open) · UPT-3730 (error code vocabulary undocumented) · UPT-3731 (integrations
discriminator). UPT-3558 was already in progress.
None of them block this release, and none block the frozen contract.