Skip to content

feat: improve agent/CI ergonomics — capabilities command, non-interactive mode, JSON error envelopes, exit-code contract#8300

Open
DavidWells wants to merge 11 commits into
mainfrom
agent-ergonomics-pass-1
Open

feat: improve agent/CI ergonomics — capabilities command, non-interactive mode, JSON error envelopes, exit-code contract#8300
DavidWells wants to merge 11 commits into
mainfrom
agent-ergonomics-pass-1

Conversation

@DavidWells

Copy link
Copy Markdown
Contributor

TL;DR

AI agents are now heavy users of netlify — and they drive it through pipes, parse its output, and branch on its exit codes. We ran a structured agent-ergonomics audit of every command, flag, and error path, then fixed the 13 highest-impact issues. Everything is additive: no existing flag, output field, or success path changes behavior.

  • Added netlify capabilities --json — machine-readable manifest of every command, flag, exit code, and env var
  • Added --non-interactive global flag — prompts fail fast with a clear message instead of hanging headless sessions
  • Fixed netlify sites delete x (space instead of colon) no longer prints help and exits 0 — it errors with the correct sites:delete suggestion
  • Fixed status --json and env:* --json always emit JSON, even on auth/link errors — no more zero-byte stdout
  • Added documented exit codes: 0 ok · 1 error · 2 usage error · 4 input needed
  • Fixed piped --help is parseable again, NO_COLOR is honored, typos get better suggestions, corrupt state files are backed up instead of silently reset

Measured with a fresh-context agent on 8 realistic tasks before/after: 5/8 → 8/8 task success, and the most common workflow (reading env vars) went from 5 round-trips to 1. Each change ships with unit tests; validation: typecheck ✓ lint ✓ 434 unit tests ✓.


The changes

1. Space-form subcommands no longer fake-succeed

Why: netlify sites delete my-site used to print the sites help text and exit 0. A script — or an agent doing cleanup — reads that as "site deleted." Nothing happened. This was the single most dangerous false-success in the CLI.

Better for agents: exit 0 now always means the command actually ran. The error names the exact colon-form command (flags preserved), so an agent self-corrects in one step.

Before / after
$ netlify sites delete my-precious-site

Before — exit 0, parent help on stdout:

Handle various project operations

USAGE
  $ netlify sites [options]
...

After — exit 2, stderr:

 ›   Error: 'netlify sites delete' is not a command.
 ›   Did you mean 'netlify sites:delete my-precious-site'?
 ›   Run 'netlify sites --help' to see available subcommands.

Also covers netlify env list, netlify functions invoke hello, etc. Trailing flags survive the suggestion (netlify env list --jsonnetlify env:list --json).

2. --json always means JSON — even on errors

Why: netlify status --json in an unlinked folder emitted zero bytes of stdout. netlify env:get FOO --json emitted zero bytes with exit 0 — indistinguishable from "the variable is empty," which is how empty secrets end up in configs.

Better for agents: any consumer of --json can now always JSON.parse stdout and branch on a stable error.code (NOT_LINKED, NOT_LOGGED_IN) with a suggested fix, instead of guessing from silence.

Before / after
$ netlify env:get DATABASE_URL --json     # in an unlinked directory

Before — exit 0, no output at all.

After — exit 1, stdout:

{
  "error": {
    "code": "NOT_LINKED",
    "message": "No project id found, please run inside a project folder or `netlify link`",
    "fix": "netlify link"
  }
}
$ netlify status --json                   # authenticated, but unlinked

Before — exit 1, zero bytes of stdout (can't tell "not logged in" from "not linked").

After — stdout always carries the answer:

{
  "loggedIn": true,
  "linked": false,
  "account": { "Name": "", "Email": "" },
  ...
}

Non-JSON output and exit semantics are unchanged. The env:* commands also now honor their documented --site flag (it was silently ignored).

3. New: netlify capabilities --json

Why: the only way to learn what this CLI can do programmatically was ~56 separate --help invocations at ~2.5s each — and the help text isn't machine-readable.

Better for agents: one call returns the full contract: all 71 commands with their flags, which support --json, the exit-code dictionary, relevant env vars, and config file locations. Output is deterministic (byte-identical across runs and locales), pipe-safe, and works in monorepos and CI.

Output shape
{
  "contract_version": "1",
  "cli_version": "26.1.0",
  "commands": [
    { "name": "env:list", "description": "", "flags": [ ], "json_output": true, "mutates": null },
    
  ],
  "global_flags": [ ],
  "exit_codes": { "0": "success", "1": "general error", "2": "usage error", "4": "non-interactive prompt blocked" },
  "env_vars": [ { "name": "NETLIFY_AUTH_TOKEN", "description": "" }, ],
  "config_files": [ { "path": "netlify.toml", "scope": "project" }, ]
}

4. New: --non-interactive — prompts fail fast instead of hanging

Why: a hidden interactive prompt in a headless session means an agent or CI job hangs forever with no diagnostic.

Better for agents: with --non-interactive (or CI=true, which already gated some prompts), any would-be prompt exits immediately with code 4 and a message naming the exact flags that would answer it.

Before / after
$ netlify link --non-interactive

Before — exit 1: unknown option '--non-interactive' (and without it, CI hangs on the prompt).

After — exit 4, stderr:

 ›   Error: Cannot prompt for input in non-interactive mode (CI or --non-interactive).
 ›   Prompt: How do you want to link this folder to a project?
No project specified. In non-interactive mode, you must specify how to link:

Link by project ID:
  netlify link --id <project-id>
...

5. Documented exit codes; usage errors are distinguishable

Why: every failure exited 1 — a typo'd flag looked identical to a failed deploy. Nothing documented the contract.

Better for agents: 0 ok / 1 error / 2 usage error / 4 input needed, documented in the --help footer and in capabilities --json. Retry logic can finally distinguish "I made a mistake" (fix the command) from "the operation failed" (maybe retry). New codes only appear on paths that already failed — nothing that exited 0 changes.

Before / after
$ netlify env:set            # missing required argument
$ netlify sties:list         # unknown command
$ netlify env:list --jsno    # unknown option

Before: all exit 1.
After: all exit 2; messages unchanged. Unknown-command suggestions also moved from stdout to stderr, keeping stdout data-clean.

6. Errors teach the fix

Why: netlify api getSite --data 'site_id=123' leaked a raw SyntaxError: Unexpected token 's'… — we found a real agent session with 18 consecutive failures on exactly this confusion.

Better for agents: the error names the flag, echoes the bad input, and shows a correct example — one failure instead of eighteen.

Before / after

Before:

 ›   SyntaxError: Unexpected token 's', "site_id=123" is not valid JSON

After:

 ›   Error: Invalid JSON provided to the --data flag.
Received: site_id=123
The --data flag expects a JSON object of API parameters, e.g. --data '{"site_id":"123456"}'.
Note: key=value pairs are not accepted; use JSON syntax instead.

Related: root-level unknown flags now name the commands that own the flag (netlify --jsno'--json' is a flag of: agents:create, agents:list, …). Subcommand-level suggestions already existed and are unchanged.

7. Piped --help is parseable again

Why: the help renderer separated flag names from descriptions with only an ANSI color code. Pipe it (which is how every agent reads help) and you get --dryDry run: show instructions… — corrupted data on the CLI's primary self-documentation surface.

Better for agents: real whitespace padding on every row, in every terminal.

Before / after
$ netlify build --help | grep dry

Before: --dryDry run: show instructions without running them (default: false)
After: --dry Dry run: show instructions without running them

8. NO_COLOR honored

Why: the informal standard (no-color.org) wasn't checked anywhere; agents that set it still got ANSI noise to strip.

Better for agents: NO_COLOR=1 produces zero escape sequences across help, status, and all prettyjson output (29 → 0 in env --help under a TTY).

9. Corrupt state files are preserved, not silently reset

Why: a malformed .netlify/state.json (or global config) was silently ignored — and could be silently wiped, losing the project link with no trace.

Better for agents: the corrupt file is backed up to <file>.corrupt.<timestamp> with a stderr warning naming both paths, then the command proceeds. Nothing is lost, and the failure is diagnosable.


Compatibility

  • No breaking changes intended. All fixes are additive; non-JSON human output is untouched except where it was broken (help gluing); exit-code changes apply only to invocations that already failed.
  • New surfaces: capabilities command, --non-interactive global flag, JSON error envelopes (additive fields), exit codes 2/4 on previously-exit-1 error paths.

Testing

  • New/extended unit tests for every change (suggestions, capabilities manifest incl. determinism, status/env envelopes, exit codes, NO_COLOR, prompt gating, corruption guard); full suite: 63 files / 434 tests green, typecheck + lint clean.
  • 12 end-to-end regression scripts (one per change) exercising bin/run.js exist in the audit workspace and can be ported into tests/integration if wanted.

How this was produced

A structured agent-ergonomics audit: 295 CLI surfaces inventoried and scored on an 11-dimension rubric (two independent scorers + tiebreaks), a 510-case wrong-invocation corpus run against the binary, mining of real agent session logs for failure frequency, recommendations cross-reviewed by two other models, and before/after validation by a fresh-context agent attempting 8 canonical tasks (5/8 → 8/8). Full artifacts (scorecards, corpus, transcripts, playbook) are available outside this PR.

…ocumented --site flag

Agent-ergonomics audit R-002 + R-010 (pass 1).
- env:* with --json in an unlinked dir now emits {"error":{"code":"NOT_LINKED",...}} on stdout and exits 1 (was: exit 0, empty output)
- prose errors go to stderr
- --site now resolves via the existing base-command siteInfo path (was: silently ignored)
…tate

Agent-ergonomics audit R-004 (pass 1).
- unlinked / logged-out / expired-token paths emit JSON envelopes with NOT_LINKED / NOT_LOGGED_IN codes (was: exit 1 with zero stdout)
- non-JSON mode semantics unchanged; happy-path JSON keys additive only
Agent-ergonomics audit R-011 (pass 1).
- JSON.parse failures name --data, echo truncated input, show a concrete example, note key=value is not accepted
- missing-path-variable errors list the method's required variables
…piped --help

Agent-ergonomics audit R-001 + R-005 + R-008 (pass 1).
- R-001: namespace parents (sites/env/functions/blobs/teams/agents/completion/open) now exit 1 with a colon-form suggestion on stderr instead of printing parent help with exit 0 ('netlify sites delete x' no longer fake-succeeds)
- R-005: unknown flags get levenshtein did-you-mean (distance<=2); root-level errors name owning commands; unknown-command suggestions moved stdout->stderr
- R-008: help formatter pads term/description with real whitespace in non-TTY output (was ANSI-color-only separator producing '--dryDry run:')
…orruption guard, capabilities command

Agent-ergonomics audit R-003 + R-006 + R-007/R-009 + R-014 + R-027 (pass 1).
- R-006: src/utils/exit-codes.ts documents 0/1/2/4; unknown command/option paths now exit 2; dictionary in root help epilogue
- R-014: NO_COLOR (no-color.org) disables colors regardless of TTY
- R-027: global --non-interactive fails would-be prompts with exit 4 naming the missing flag/env var (extends CI=true gate)
- R-007/R-009: corrupt .netlify/state.json / global config backed up to <file>.corrupt.<mtime> with stderr warning before dev-utils would silently reset it
- R-003: netlify capabilities [--json] emits a deterministic machine-readable manifest (71 commands, flags, json support, exit codes, env vars, config paths)
…ge errors exit 2, capabilities full-path names, colon-form hints keep flags

Agent-ergonomics audit phase 7 round 1.
- prettyjson render sites (status, status:hooks, deploy, sites:create, watch) honor NO_COLOR/--json
- USAGE_ERROR_CODES covers missingArgument/optionMissingArgument/invalidArgument/etc -> exit 2
- capabilities manifest uses full command paths (database status), deduped, deterministic
- space-form suggestions preserve trailing flags ('netlify env list --json' -> suggests 'netlify env:list --json')
… stop reading early

Agent-ergonomics audit phase 7 round 2. 'netlify capabilities | head' hit the
uncaughtException crash banner; stdout error listener now treats EPIPE as success.
Agent-ergonomics audit phase 7 round 3. localeCompare collation varies with
LANG/LC_ALL; contract-versioned manifest now sorts by code units and the
determinism test asserts the same comparator.
Agent-ergonomics audit phase 7 round 5. capabilities needs no project context;
in a multi-package workspace under CI/--non-interactive the preAction workspace
prompt blocked it with exit 4 and empty stdout.
@DavidWells DavidWells requested a review from a team as a code owner June 11, 2026 23:13
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02078ad6-223c-4887-ac29-729e07e2d910

📥 Commits

Reviewing files that changed from the base of the PR and between a5097fc and ecc0437.

⛔ Files ignored due to path filters (3)
  • tests/integration/commands/didyoumean/__snapshots__/didyoumean.test.ts.snap is excluded by !**/*.snap
  • tests/integration/commands/env/__snapshots__/env.test.ts.snap is excluded by !**/*.snap
  • tests/integration/commands/help/__snapshots__/help.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • docs/commands/capabilities.md
  • docs/index.md
  • src/commands/base-command.ts
  • src/commands/main.ts
  • tests/integration/commands/didyoumean/didyoumean.test.ts
  • tests/integration/commands/env/env.test.ts
  • tests/unit/commands/env/utils.test.ts
  • tests/unit/commands/main-suggestions.test.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • netlify/blueprints (manual)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/commands/env/utils.test.ts
  • tests/unit/commands/main-suggestions.test.ts

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added capabilities command to output machine-readable CLI manifest (commands, flags, exit codes, environment variables, config locations)
    • Added --non-interactive flag for scripted/CI environments with improved error guidance
    • Added support for NO_COLOR environment variable to disable color output
  • Bug Fixes

    • Improved detection and automatic backup of corrupted JSON config files
    • Enhanced error messages for missing required API path variables
    • Smarter suggestions for typos in commands and options
  • Documentation

    • Added documentation for capabilities command

Walkthrough

This PR introduces structured exit code handling (0, 1, 2, 4), non-interactive mode support via --non-interactive flag, and configuration file corruption safeguards. It adds a Levenshtein-based command suggestion engine for unknown flags, registers a new capabilities command that outputs machine-readable CLI metadata, and enhances the API command's --data parsing with better error messages. Environment commands are refactored to centralize site ID resolution and standardize "not linked" error handling. The status command gains JSON error codes and extended response fields. Help argument validation is unified across commands, and JSON output formatting is standardized via prettyJsonRenderOptions().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • serhalp
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective of the PR: improving agent and CI ergonomics through four major additions (capabilities command, non-interactive mode, JSON error envelopes, exit-code contract).
Description check ✅ Passed The description comprehensively explains the motivation, implementation, and impact of all changes with concrete before/after examples, test coverage, and compatibility notes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent-ergonomics-pass-1

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed due to a network error.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

📊 Benchmark results

Comparing with 392af83

  • Dependency count: 1,127 (no change)
  • Package size: 379 MB (no change)
  • Number of ts-expect-error directives: 354 (no change)

@DavidWells DavidWells changed the title Make the CLI safe and legible for AI agents (and CI scripts) feat: improve agent/CI ergonomics — capabilities command, non-interactive mode, JSON error envelopes, exit-code contract Jun 11, 2026
@DavidWells DavidWells requested a review from a team as a code owner June 11, 2026 23:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/unit/commands/env/utils.test.ts (1)

1-87: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix CI formatting failure in this test file.

Format check is failing for this file in CI (Prettier --check). Please run Prettier on this file and commit the result so the format job passes.

🤖 Prompt for 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.

In `@tests/unit/commands/env/utils.test.ts` around lines 1 - 87, The file
tests/unit/commands/env/utils.test.ts is failing Prettier --check; reformat it
with your project's formatter (e.g. run prettier --write
tests/unit/commands/env/utils.test.ts or the repo's format script) and commit
the changes so CI passes; you can verify the sections around the getEnvSiteId
and failNotLinked tests (and the vi.mock block at the top) are properly
spaced/quoted and end-of-line/newline rules conform to Prettier.

Source: Pipeline failures

🧹 Nitpick comments (2)
src/commands/main.ts (1)

214-216: ⚡ Quick win

Consolidated: remove behavior-describing comments across src/commands/main.ts and src/commands/capabilities/capabilities.ts.

Both files introduce inline comments that explain runtime behavior instead of making intent self-evident through naming/structure, which conflicts with the same repository rule.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Never write comments on what the code does; make the code clean and self-explanatory instead".

🤖 Prompt for 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.

In `@src/commands/main.ts` around lines 214 - 216, Remove the inline
behavior-describing comments in src/commands/main.ts (around the isInteractive()
check) and src/commands/capabilities/capabilities.ts and instead make the intent
explicit via clearer names or small helper functions; for example, replace a
naked if (!isInteractive()) comment block by extracting a well-named function
like handleNonInteractiveOutput or use a constant named DIAGNOSTICS_TO_STDERR to
express why stderr is used, and in capabilities.ts rename or split ambiguous
code into self-describing functions/variables so the code reads without
explanatory comments.

Source: Coding guidelines

src/utils/command-helpers.ts (1)

36-42: ⚡ Quick win

Consolidated: remove newly added behavior-describing comments across src/utils/command-helpers.ts, src/utils/scripted-commands.ts, and src/utils/command-error-handler.ts.

The shared root cause is the same style-rule breach: new TS/JS comments that narrate behavior rather than letting names/structure carry intent.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Never write comments on what the code does; make the code clean and self-explanatory instead.

🤖 Prompt for 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.

In `@src/utils/command-helpers.ts` around lines 36 - 42, Remove the
behavior-describing comments that narrate what the code does and instead rely on
clear names and structure: delete the comment above shouldDisableColors and the
one above chalk/prettyJsonRenderOptions in src/utils/command-helpers.ts, and
likewise remove similar explanatory comments in src/utils/scripted-commands.ts
and src/utils/command-error-handler.ts; leave function/constant names
(shouldDisableColors, chalk, prettyJsonRenderOptions and any exported
handlers/commands) intact and ensure any remaining comments are focused on why
(intent) or non-obvious implementation details only.

Source: Coding guidelines

🤖 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 `@src/commands/api/api.ts`:
- Around line 65-75: The current cast of methods to a specific shape is unsafe
and can throw when building the missing-path error message; add a runtime type
guard (e.g., isApiMethod(obj): obj has operationId:string and optional
parameters.path:Record<string,unknown>) and use it to filter/map methods before
calling find by apiMethodName, then safely read parameters?.path and guard
Object.keys with a default empty object; update the block that computes
apiMethods, pathVariables and requiredNames (references: methods, apiMethods,
apiMethodName, parameters.path, logAndThrowError) to use these checks so the
error path never assumes the shape.

In `@src/commands/main.ts`:
- Line 207: Run Prettier to format src/commands/main.ts and commit the changes
so CI passes; specifically reformat the offending write call that uses
process.stderr.write with chalk and BANG (the expression containing
`process.stderr.write(\` ${chalk.yellow(BANG)}   Warning:
${chalk.yellow(command.args[0])} is not a ${command.name()} command.\n\`)`) and
the similar hunk around `command.args[0]`/`command.name()` so spacing, quotes,
and line breaks match project Prettier rules; after running Prettier verify the
file is saved and staged, then push the formatted file.

In `@src/utils/config-guard.ts`:
- Around line 38-41: Wrap the synchronous filesystem operations that build and
write the backup (the statSync(...) call used to compute backupPath and the
copyFileSync(...) call) in try/catch so any EIO/ENOENT/EPERM errors do not
propagate and abort initialization; on statSync failure fall back to a safe
timestamp (e.g., Date.now()) when composing backupPath, and on copyFileSync
failure catch and log a non-fatal warning (using the module/logger available)
and continue without throwing. Ensure you keep the existsSync(...) check but
still guard copyFileSync with try/catch so removal/permission changes between
existsSync and copyFileSync won’t crash the process.

---

Outside diff comments:
In `@tests/unit/commands/env/utils.test.ts`:
- Around line 1-87: The file tests/unit/commands/env/utils.test.ts is failing
Prettier --check; reformat it with your project's formatter (e.g. run prettier
--write tests/unit/commands/env/utils.test.ts or the repo's format script) and
commit the changes so CI passes; you can verify the sections around the
getEnvSiteId and failNotLinked tests (and the vi.mock block at the top) are
properly spaced/quoted and end-of-line/newline rules conform to Prettier.

---

Nitpick comments:
In `@src/commands/main.ts`:
- Around line 214-216: Remove the inline behavior-describing comments in
src/commands/main.ts (around the isInteractive() check) and
src/commands/capabilities/capabilities.ts and instead make the intent explicit
via clearer names or small helper functions; for example, replace a naked if
(!isInteractive()) comment block by extracting a well-named function like
handleNonInteractiveOutput or use a constant named DIAGNOSTICS_TO_STDERR to
express why stderr is used, and in capabilities.ts rename or split ambiguous
code into self-describing functions/variables so the code reads without
explanatory comments.

In `@src/utils/command-helpers.ts`:
- Around line 36-42: Remove the behavior-describing comments that narrate what
the code does and instead rely on clear names and structure: delete the comment
above shouldDisableColors and the one above chalk/prettyJsonRenderOptions in
src/utils/command-helpers.ts, and likewise remove similar explanatory comments
in src/utils/scripted-commands.ts and src/utils/command-error-handler.ts; leave
function/constant names (shouldDisableColors, chalk, prettyJsonRenderOptions and
any exported handlers/commands) intact and ensure any remaining comments are
focused on why (intent) or non-obvious implementation details only.
🪄 Autofix (Beta)

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: CHILL

Plan: Pro

Run ID: 618cda5f-b77a-4e1d-aba8-efd4d34481cd

📥 Commits

Reviewing files that changed from the base of the PR and between 392af83 and a5097fc.

📒 Files selected for processing (44)
  • src/commands/agents/agents.ts
  • src/commands/api/api.ts
  • src/commands/base-command.ts
  • src/commands/blobs/blobs.ts
  • src/commands/capabilities/capabilities.ts
  • src/commands/capabilities/index.ts
  • src/commands/completion/index.ts
  • src/commands/deploy/deploy.ts
  • src/commands/env/env-clone.ts
  • src/commands/env/env-get.ts
  • src/commands/env/env-import.ts
  • src/commands/env/env-list.ts
  • src/commands/env/env-set.ts
  • src/commands/env/env-unset.ts
  • src/commands/env/env.ts
  • src/commands/env/utils.ts
  • src/commands/functions/functions.ts
  • src/commands/link/link.ts
  • src/commands/main.ts
  • src/commands/open/index.ts
  • src/commands/sites/sites-create.ts
  • src/commands/sites/sites.ts
  • src/commands/status/status-hooks.ts
  • src/commands/status/status.ts
  • src/commands/teams/teams.ts
  • src/commands/watch/watch.ts
  • src/utils/command-error-handler.ts
  • src/utils/command-helpers.ts
  • src/utils/config-guard.ts
  • src/utils/exit-codes.ts
  • src/utils/run-program.ts
  • src/utils/scripted-commands.ts
  • tests/integration/commands/dev/dev-miscellaneous.test.ts
  • tests/unit/commands/api/api.test.ts
  • tests/unit/commands/capabilities/capabilities.test.ts
  • tests/unit/commands/env/env-get.test.ts
  • tests/unit/commands/env/utils.test.ts
  • tests/unit/commands/help-format.test.ts
  • tests/unit/commands/main-suggestions.test.ts
  • tests/unit/commands/status/status.test.ts
  • tests/unit/utils/config-guard.test.ts
  • tests/unit/utils/exit-codes.test.ts
  • tests/unit/utils/no-color.test.ts
  • tests/unit/utils/scripted-commands.test.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • netlify/blueprints (manual)

Comment thread src/commands/api/api.ts
Comment on lines +65 to +75
if (error_ instanceof Error && error_.message.includes('Missing required path variable')) {
const apiMethods = methods as { operationId: string; parameters: { path?: Record<string, unknown> } }[]
const pathVariables = apiMethods.find((method) => method.operationId === apiMethodName)?.parameters.path ?? {}
const requiredNames = Object.keys(pathVariables).join(', ')
return logAndThrowError(
`${error_.message}
The ${chalk.cyanBright('--data')} flag must include the path variable(s) required by ${apiMethodName}${
requiredNames ? `: ${requiredNames}` : ''
}, e.g. --data '{"site_id":"123456"}'`,
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the type definition of methods from `@netlify/api`

# Check if `@netlify/api` has type definitions
rg -n "export.*methods" node_modules/@netlify/api --type=ts -A 5

# Also check for the type definition in .d.ts files
fd -e d.ts . node_modules/@netlify/api --exec rg -n "methods" -A 10

Repository: netlify/cli

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant section around the snippet
sed -n '1,140p' src/commands/api/api.ts | nl -ba | sed -n '40,120p'

# Find how `methods` is imported/typed in this file
rg -n "import .*methods|from ['\"]`@netlify/api`['\"]" src/commands/api/api.ts

# Check other usages of `methods` in the repo (to understand assumed runtime shape)
rg -n "\bmethods\b" src | head -n 200

Repository: netlify/cli

Length of output: 98


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show relevant section around the snippet without `nl`
sed -n '40,120p' src/commands/api/api.ts | sed -n '1,80p'

# Find how `methods` is imported/typed in this file
rg -n "import .*methods|from ['\"]`@netlify/api`['\"]" src/commands/api/api.ts

# Check other usages of `methods` in this repo (limits output)
rg -n "\bmethods\b" src | head -n 200

Repository: netlify/cli

Length of output: 2933


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the code around the snippet and the earlier usage of `methods`
sed -n '1,120p' src/commands/api/api.ts
sed -n '120,220p' src/commands/api/api.ts

# Inspect `@netlify/api` typing for `methods` and related types
sed -n '1,120p' node_modules/@netlify/api/lib/index.d.ts

# Search for more specific types/shapes around methods/operationId/parameters.path
rg -n "operationId|parameters|path\\??" node_modules/@netlify/api/lib/index.d.ts
rg -n "interface .*method|type .*method|declare const methods" node_modules/@netlify/api/lib -S

Repository: netlify/cli

Length of output: 5169


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "\bmethods\.|methods\b.*operationId|as \{ operationId" src
rg -n "from '`@netlify/api`'" src

Repository: netlify/cli

Length of output: 2267


Guard against unchecked methods shape when building the “missing path variable” message

@netlify/api types methods as any[], so the cast to { operationId: string; parameters: { path?: Record<string, unknown> } }[] is unchecked and can fail at runtime if the element shape differs. Add runtime guards/safe access before reading operationId and parameters.path (or extract via a small helper type-guard) so this error-path doesn’t crash.

🤖 Prompt for 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.

In `@src/commands/api/api.ts` around lines 65 - 75, The current cast of methods to
a specific shape is unsafe and can throw when building the missing-path error
message; add a runtime type guard (e.g., isApiMethod(obj): obj has
operationId:string and optional parameters.path:Record<string,unknown>) and use
it to filter/map methods before calling find by apiMethodName, then safely read
parameters?.path and guard Object.keys with a default empty object; update the
block that computes apiMethods, pathVariables and requiredNames (references:
methods, apiMethods, apiMethodName, parameters.path, logAndThrowError) to use
these checks so the error path never assumes the shape.

Comment thread src/commands/main.ts Outdated
Comment thread src/utils/config-guard.ts
Comment on lines +38 to +41
const backupPath = `${filePath}.corrupt.${String(Math.round(statSync(filePath).mtimeMs))}`
if (!existsSync(backupPath)) {
copyFileSync(filePath, backupPath)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle backup I/O failures without aborting command initialization.

On Line 38 through Line 41, statSync()/copyFileSync() can throw in the corrupt-file path (e.g., readonly home/config dir or file removed between read/stat). That exception escapes and can crash command startup before recovery logic runs.

Suggested fix
   } catch {
-    const backupPath = `${filePath}.corrupt.${String(Math.round(statSync(filePath).mtimeMs))}`
-    if (!existsSync(backupPath)) {
-      copyFileSync(filePath, backupPath)
-    }
-    writeCorruptFileWarning(filePath, backupPath, recoveryHint)
-    return backupPath
+    try {
+      const backupPath = `${filePath}.corrupt.${String(Math.round(statSync(filePath).mtimeMs))}`
+      if (!existsSync(backupPath)) {
+        copyFileSync(filePath, backupPath)
+      }
+      writeCorruptFileWarning(filePath, backupPath, recoveryHint)
+      return backupPath
+    } catch {
+      process.stderr.write(` ${chalk.yellow(BANG)}   Warning: ${filePath} contains malformed JSON and could not be backed up.\n`)
+      process.stderr.write(` ${chalk.yellow(BANG)}   Repair the file manually, or ${recoveryHint}.\n`)
+      return undefined
+    }
   }
🤖 Prompt for 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.

In `@src/utils/config-guard.ts` around lines 38 - 41, Wrap the synchronous
filesystem operations that build and write the backup (the statSync(...) call
used to compute backupPath and the copyFileSync(...) call) in try/catch so any
EIO/ENOENT/EPERM errors do not propagate and abort initialization; on statSync
failure fall back to a safe timestamp (e.g., Date.now()) when composing
backupPath, and on copyFileSync failure catch and log a non-fatal warning (using
the module/logger available) and continue without throwing. Ensure you keep the
existsSync(...) check but still guard copyFileSync with try/catch so
removal/permission changes between existsSync and copyFileSync won’t crash the
process.

- env:clone unlinked now exits 1 with teaching error (was exit 0 + snapshot)
- didyoumean suggestions moved to stderr; assert reason.stderr
- help snapshot includes the new exit-codes epilogue
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