Skip to content

feat: support a create with agent runners flow in CLI#8066

Open
sean-roberts wants to merge 13 commits intomainfrom
sr/sites
Open

feat: support a create with agent runners flow in CLI#8066
sean-roberts wants to merge 13 commits intomainfrom
sr/sites

Conversation

@sean-roberts
Copy link
Contributor

@sean-roberts sean-roberts commented Mar 19, 2026

Summary

Adds netlify create — a new command that creates a Netlify project end-to-end using an AI agent. Given a natural language prompt, it:

  • Creates a new Netlify site
  • Starts an AI agent runner (defaults to Claude) that builds the site
  • Polls for completion, streaming agent progress to the terminal
  • Downloads the generated source code locally and links the project
  • Optionally creates a private GitHub repo, pushes the source via Netlify's API, and initializes the local git repo (--git github)

Key flags

Flag Description
--agent <type> Agent type (claude, codex, gemini) — defaults to claude
--name <name> Project name (subdomain) with automatic collision retry
--git <provider> Create a GitHub repo and push source (e.g. github)
--repo-owner <owner> GitHub org or user for the repo (skips interactive prompt)
--account-slug <slug> Team to create the project under
--no-download Skip downloading source code locally
--no-wait Return immediately after starting the agent run
--json Output result as JSON

Examples

netlify create "a portfolio site"
netlify create "a blog with dark mode" --name my-blog --git github
netlify create "landing page" --account-slug my-team --no-wait

Also adds netlify sites:create --prompt as an alias that delegates to the same flow.

Test plan

  • 23 integration tests covering: --no-wait, polling, error handling, validation, interactive prompts, --name collision retry, source download, --git flag, and API request shape
  • Help command snapshot updated
  • TypeScript compiles, ESLint and Prettier clean
  • Manual: netlify create "hello world" --git github — verify full flow with org prompt, source download, and local git init

🤖 Generated with Claude Code

@sean-roberts sean-roberts requested review from a team as code owners March 19, 2026 15:44
@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new CLI command netlify create with documentation and index entries, registers the command, and implements its handler that authenticates, resolves prompt/agent/account inputs, creates a site (with name-collision retry), starts an agent runner, optionally polls runner state, downloads/extracts session source, links the local project, and emits JSON or human-readable output. Also adds a --prompt passthrough to sites:create, extends AgentRunner with latest_session_deploy_id, and includes an extensive integration test suite exercising success, error, retry, and interactive paths.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: support a create with agent runners flow in CLI' directly and clearly summarizes the main change: adding a new create command that uses agent runners.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description clearly explains the new netlify create command functionality, flags, examples, and test coverage, all directly related to the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sr/sites

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

@github-actions
Copy link

github-actions bot commented Mar 19, 2026

📊 Benchmark results

Comparing with a5d6373

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/commands/create/create-action.ts (2)

226-242: Consider adding a timeout to the polling loop.

The polling loop runs indefinitely until a terminal state is reached. If the API experiences issues or the agent gets stuck in a non-terminal state, this could hang forever.

💡 Suggested enhancement
+const MAX_POLL_DURATION = 30 * 60 * 1000 // 30 minutes
+
 // Step 3: Poll for completion
 const pollSpinner = startSpinner({ text: 'Agent is working...' })
 let lastTask = ''
+const pollStartTime = Date.now()

 try {
   // eslint-disable-next-line `@typescript-eslint/no-unnecessary-condition`
   while (true) {
     await sleep(POLL_INTERVAL)

+    if (Date.now() - pollStartTime > MAX_POLL_DURATION) {
+      throw new Error('Polling timeout exceeded. Use --no-wait to avoid waiting.')
+    }
+
     const runner = await fetchAgentRunner(agentRunner.id, api, apiOpts)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/create/create-action.ts` around lines 226 - 242, The polling
loop in create-action.ts (the while(true) block that calls fetchAgentRunner,
referencing agentRunner, lastTask, POLL_INTERVAL, TERMINAL_STATES and
pollSpinner) can hang indefinitely; add a timeout/deadline or max-attempts
guard: record a start timestamp or attempt counter before the loop, check
elapsed time or attempts each iteration and break/throw a descriptive error if
exceeded, and ensure pollSpinner is updated (e.g., set to an error message) and
agentRunner is set appropriately before exiting; implement this by modifying the
existing while(true) loop to bail out when timeout/attempts reached instead of
looping forever.

136-139: Potential orphaned site if agent runner creation fails.

If site creation succeeds (line 127) but agent runner creation fails (line 174-176), the site remains without an agent run. Consider whether to clean up the orphaned site or document this as expected behavior.

💭 Possible cleanup approach
 } catch (error_) {
   stopSpinner({ spinner: agentSpinner, error: true })
+  // Optionally clean up the orphaned site
+  log(chalk.dim(`Note: Project ${site.name} was created but agent could not be started.`))
   return logAndThrowError(`Failed to start agent: ${(error_ as Error).message}`)
 }

Alternatively, you could attempt to delete the site on failure, but that adds complexity and may not be desired if the user wants to retry manually.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/create/create-action.ts` around lines 136 - 139, If site
creation can succeed but subsequent agent runner creation (the block that
creates the agent runner) fails, avoid leaving an orphaned site by performing
cleanup: after stopSpinner({ spinner: siteSpinner, error: true }) and before
returning from the catch that currently calls logAndThrowError, attempt to
delete the newly created site (use your existing site-delete/cleanup helper such
as deleteSite or cleanupSite with the created siteId returned by the create-site
step) and log any cleanup errors separately; ensure you still return the
original creation error via logAndThrowError so the primary failure is preserved
and any cleanup failure is non-fatal and only logged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/integration/commands/create/create.test.ts`:
- Around line 408-419: The test uses a non-null assertion on agentRunnerRequest
when accessing .body; replace the non-null assertion (agentRunnerRequest!.body)
with optional chaining (agentRunnerRequest?.body) so the linter error is
resolved and the assertion becomes
expect(agentRunnerRequest?.body).toEqual(...); modify the assertion in the block
where agentRunnerRequest is defined to use agentRunnerRequest?.body instead of
agentRunnerRequest!.
- Around line 381-388: The test uses a non-null assertion on siteCreateRequest
(variable created by requests.find) which violates lint rules; after
expect(siteCreateRequest).toBeDefined() TypeScript doesn't narrow the type, so
remove the `!` and either guard or use optional chaining: replace usages like
siteCreateRequest!.body with siteCreateRequest?.body inside the expect (e.g.,
expect(siteCreateRequest?.body).toEqual(...)) or add an explicit runtime guard
(if (!siteCreateRequest) throw new Error('expected siteCreateRequest');) before
asserting on siteCreateRequest.body to satisfy the linter; update the assertion
line referencing siteCreateRequest.body accordingly.

---

Nitpick comments:
In `@src/commands/create/create-action.ts`:
- Around line 226-242: The polling loop in create-action.ts (the while(true)
block that calls fetchAgentRunner, referencing agentRunner, lastTask,
POLL_INTERVAL, TERMINAL_STATES and pollSpinner) can hang indefinitely; add a
timeout/deadline or max-attempts guard: record a start timestamp or attempt
counter before the loop, check elapsed time or attempts each iteration and
break/throw a descriptive error if exceeded, and ensure pollSpinner is updated
(e.g., set to an error message) and agentRunner is set appropriately before
exiting; implement this by modifying the existing while(true) loop to bail out
when timeout/attempts reached instead of looping forever.
- Around line 136-139: If site creation can succeed but subsequent agent runner
creation (the block that creates the agent runner) fails, avoid leaving an
orphaned site by performing cleanup: after stopSpinner({ spinner: siteSpinner,
error: true }) and before returning from the catch that currently calls
logAndThrowError, attempt to delete the newly created site (use your existing
site-delete/cleanup helper such as deleteSite or cleanupSite with the created
siteId returned by the create-site step) and log any cleanup errors separately;
ensure you still return the original creation error via logAndThrowError so the
primary failure is preserved and any cleanup failure is non-fatal and only
logged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8426fdf1-261f-40d8-8028-a568007c9004

📥 Commits

Reviewing files that changed from the base of the PR and between bb81ca7 and e8a41a7.

📒 Files selected for processing (7)
  • docs/commands/create.md
  • docs/index.md
  • src/commands/create/create-action.ts
  • src/commands/create/create.ts
  • src/commands/create/index.ts
  • src/commands/main.ts
  • tests/integration/commands/create/create.test.ts

@sean-roberts sean-roberts changed the title Sr/sites feat: support a create with agent runners flow in CLI Mar 19, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
tests/integration/commands/create/create.test.ts (3)

381-388: ⚠️ Potential issue | 🟡 Minor

Fix non-null assertion to resolve lint failure.

The pipeline is failing due to forbidden non-null assertions. Use optional chaining instead.

🔧 Proposed fix
         const siteCreateRequest = requests.find(
           (r) => r.path === '/api/v1/test-account/sites' && r.method === 'POST',
         )
         expect(siteCreateRequest).toBeDefined()
-        expect(siteCreateRequest!.body).toEqual(
+        expect(siteCreateRequest?.body).toEqual(
           expect.objectContaining({ name: 'my-cool-site', created_via: 'agent_runner' }),
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/commands/create/create.test.ts` around lines 381 - 388, The
test uses a non-null assertion on siteCreateRequest (siteCreateRequest!) which
violates the lint rule; change assertions to use optional chaining and guarded
expectations instead—e.g., assert siteCreateRequest is defined with
expect(siteCreateRequest).toBeDefined() and then access body via optional
chaining (siteCreateRequest?.body) in the subsequent expect, or restructure the
test to first throw if undefined and then call expect(siteCreateRequest.body).
Use the variable name siteCreateRequest and the existing expect calls
(toBeDefined, toEqual) when applying the fix.

489-500: ⚠️ Potential issue | 🟡 Minor

Fix non-null assertion to resolve lint failure.

Use optional chaining instead of non-null assertion.

🔧 Proposed fix
         const agentRunnerRequest = requests.find(
           (r) => r.path.includes('agent_runners') && r.method === 'POST',
         )
         expect(agentRunnerRequest).toBeDefined()
-        expect(agentRunnerRequest!.body).toEqual(
+        expect(agentRunnerRequest?.body).toEqual(
           expect.objectContaining({
             mode: 'create',
             prompt: 'Build a blog with dark mode',
             agent: 'claude',
           }),
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/commands/create/create.test.ts` around lines 489 - 500, The
test uses a non-null assertion on agentRunnerRequest (agentRunnerRequest!) which
triggers a lint failure; change the assertion to use optional chaining or safe
access—replace the non-null assertion in the expect call with
agentRunnerRequest?.body (e.g., expect(agentRunnerRequest?.body).toEqual(...))
so the prior expect(agentRunnerRequest).toBeDefined() guarantees presence while
avoiding the non-null operator in the assertion referencing agentRunnerRequest
in the create.test.ts snippet.

462-469: ⚠️ Potential issue | 🟡 Minor

Fix non-null assertion to resolve lint failure.

Use optional chaining instead of non-null assertion.

🔧 Proposed fix
         const siteCreateRequest = requests.find(
           (r) => r.path === '/api/v1/test-account/sites' && r.method === 'POST',
         )
         expect(siteCreateRequest).toBeDefined()
-        expect(siteCreateRequest!.body).toEqual(
+        expect(siteCreateRequest?.body).toEqual(
           expect.objectContaining({ created_via: 'agent_runner' }),
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/commands/create/create.test.ts` around lines 462 - 469, The
test uses a non-null assertion on siteCreateRequest (siteCreateRequest!) which
causes a lint failure; change the assertion to use optional chaining
instead—after the existing expect(siteCreateRequest).toBeDefined() keep that
check and replace the non-null assertion with siteCreateRequest?.body in the
subsequent expect call so the line referencing siteCreateRequest's body uses
optional chaining; locate the siteCreateRequest variable in the create.test.ts
integration test and update the expect(siteCreateRequest!.body).toEqual(...) to
use optional chaining.
🧹 Nitpick comments (1)
src/commands/create/create-action.ts (1)

25-46: Consider adding a timeout to prevent indefinite hangs.

The fetch call has no timeout configured. If the server becomes unresponsive during polling, this could hang indefinitely. Consider using AbortController with a timeout.

♻️ Suggested improvement
 const fetchAgentRunner = async (
   id: string,
   api: { accessToken?: string | null; host: string },
   apiOpts: { scheme?: string; host?: string; userAgent: string },
 ): Promise<AgentRunner> => {
+  const controller = new AbortController()
+  const timeoutId = setTimeout(() => controller.abort(), 30000)
+
   const response = await fetch(
     `${apiOpts.scheme ?? 'https'}://${apiOpts.host ?? api.host}/api/v1/agent_runners/${id}`,
     {
       headers: {
         Authorization: `Bearer ${api.accessToken ?? ''}`,
         'User-Agent': apiOpts.userAgent,
       },
+      signal: controller.signal,
     },
   )
+  clearTimeout(timeoutId)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/create/create-action.ts` around lines 25 - 46, The fetch in
fetchAgentRunner can hang indefinitely; add an AbortController with a
configurable timeout (e.g., default 10s) inside fetchAgentRunner, pass
controller.signal to the fetch call, and start a timer that calls
controller.abort() after the timeout; clear the timer once the response is
received and handle aborts by catching the abort error and throwing a clear
Error (e.g., "request timed out") before returning the parsed AgentRunner.
Ensure you update the fetch invocation and error handling around response.json()
to work correctly with the abort signal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/create/create-action.ts`:
- Line 1: Run Prettier on the file containing the import of OptionValues (the
create-action module) to fix formatting; reformat the file with your project's
Prettier configuration (e.g., via npx prettier --write or your IDE), review that
the import "OptionValues" and surrounding code are properly formatted, then
stage and commit the formatted file so the Prettier check passes in CI.

In `@tests/integration/commands/create/create.test.ts`:
- Line 1: The file containing the import line "import { beforeEach, describe,
expect, test, vi } from 'vitest'" failed Prettier formatting; run the project's
formatter (e.g., Prettier or the npm/yarn format script) on this test file and
commit the resulting changes so the import and surrounding code are formatted to
project standards; ensure you run the same command used in CI (for example "npm
run format" or "npx prettier --write") and verify formatting passes locally
before pushing.

---

Duplicate comments:
In `@tests/integration/commands/create/create.test.ts`:
- Around line 381-388: The test uses a non-null assertion on siteCreateRequest
(siteCreateRequest!) which violates the lint rule; change assertions to use
optional chaining and guarded expectations instead—e.g., assert
siteCreateRequest is defined with expect(siteCreateRequest).toBeDefined() and
then access body via optional chaining (siteCreateRequest?.body) in the
subsequent expect, or restructure the test to first throw if undefined and then
call expect(siteCreateRequest.body). Use the variable name siteCreateRequest and
the existing expect calls (toBeDefined, toEqual) when applying the fix.
- Around line 489-500: The test uses a non-null assertion on agentRunnerRequest
(agentRunnerRequest!) which triggers a lint failure; change the assertion to use
optional chaining or safe access—replace the non-null assertion in the expect
call with agentRunnerRequest?.body (e.g.,
expect(agentRunnerRequest?.body).toEqual(...)) so the prior
expect(agentRunnerRequest).toBeDefined() guarantees presence while avoiding the
non-null operator in the assertion referencing agentRunnerRequest in the
create.test.ts snippet.
- Around line 462-469: The test uses a non-null assertion on siteCreateRequest
(siteCreateRequest!) which causes a lint failure; change the assertion to use
optional chaining instead—after the existing
expect(siteCreateRequest).toBeDefined() keep that check and replace the non-null
assertion with siteCreateRequest?.body in the subsequent expect call so the line
referencing siteCreateRequest's body uses optional chaining; locate the
siteCreateRequest variable in the create.test.ts integration test and update the
expect(siteCreateRequest!.body).toEqual(...) to use optional chaining.

---

Nitpick comments:
In `@src/commands/create/create-action.ts`:
- Around line 25-46: The fetch in fetchAgentRunner can hang indefinitely; add an
AbortController with a configurable timeout (e.g., default 10s) inside
fetchAgentRunner, pass controller.signal to the fetch call, and start a timer
that calls controller.abort() after the timeout; clear the timer once the
response is received and handle aborts by catching the abort error and throwing
a clear Error (e.g., "request timed out") before returning the parsed
AgentRunner. Ensure you update the fetch invocation and error handling around
response.json() to work correctly with the abort signal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 19980550-8924-4cb7-b271-ff8876c208de

📥 Commits

Reviewing files that changed from the base of the PR and between e8a41a7 and c2abda3.

📒 Files selected for processing (4)
  • docs/commands/create.md
  • src/commands/create/create-action.ts
  • src/commands/create/create.ts
  • tests/integration/commands/create/create.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/create/create.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/commands/create/create-action.ts (1)

244-271: Consider adding a polling timeout.

The polling loop will run indefinitely if the agent runner never reaches a terminal state (e.g., due to server-side issues). Consider adding a maximum polling duration with a helpful message directing users to check the agent run URL.

♻️ Example timeout implementation
   // Step 3: Poll for completion
   const pollSpinner = startSpinner({ text: 'Agent is working...' })
   let lastTask = ''
+  const POLL_TIMEOUT_MS = 30 * 60 * 1000 // 30 minutes
+  const pollStartTime = Date.now()

   try {
     // eslint-disable-next-line `@typescript-eslint/no-unnecessary-condition`
     while (true) {
       await sleep(POLL_INTERVAL)

+      if (Date.now() - pollStartTime > POLL_TIMEOUT_MS) {
+        stopSpinner({ spinner: pollSpinner })
+        log()
+        log(`${chalk.yellow('⚠')} Polling timed out. The agent is still running.`)
+        log(`  View progress: ${chalk.blue(agentRunUrl)}`)
+        log(`  Check status:  ${chalk.cyan(showCmd)}`)
+        return
+      }
+
       const runner = await fetchAgentRunner(agentRunner.id, api, apiOpts)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/create/create-action.ts` around lines 244 - 271, The polling
loop using POLL_INTERVAL and fetchAgentRunner(agentRunner.id, api, apiOpts) can
run forever; add a max timeout (e.g., MAX_POLL_DURATION_MS constant) and record
a start timestamp before the while loop, then on each iteration check elapsed
time and if it exceeds the timeout stopSpinner(pollSpinner, { error: true }),
log a clear timeout message including agentRunUrl, and call logAndThrowError to
surface the timeout; update the flow in create-action.ts around the
startSpinner/poll loop (references: startSpinner, pollSpinner, lastTask,
TERMINAL_STATES, fetchAgentRunner, agentRunner, agentRunUrl, stopSpinner,
logAndThrowError) so polling exits gracefully when the timeout is reached.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/commands/create/create-action.ts`:
- Around line 244-271: The polling loop using POLL_INTERVAL and
fetchAgentRunner(agentRunner.id, api, apiOpts) can run forever; add a max
timeout (e.g., MAX_POLL_DURATION_MS constant) and record a start timestamp
before the while loop, then on each iteration check elapsed time and if it
exceeds the timeout stopSpinner(pollSpinner, { error: true }), log a clear
timeout message including agentRunUrl, and call logAndThrowError to surface the
timeout; update the flow in create-action.ts around the startSpinner/poll loop
(references: startSpinner, pollSpinner, lastTask, TERMINAL_STATES,
fetchAgentRunner, agentRunner, agentRunUrl, stopSpinner, logAndThrowError) so
polling exits gracefully when the timeout is reached.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a8952e52-ce74-4079-b692-7a4acc9ea0a8

📥 Commits

Reviewing files that changed from the base of the PR and between c2abda3 and b6ae4f5.

📒 Files selected for processing (4)
  • docs/commands/create.md
  • src/commands/create/create-action.ts
  • src/commands/create/create.ts
  • tests/integration/commands/create/create.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/commands/create.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/commands/create/create.ts
  • tests/integration/commands/create/create.test.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/sites/sites.ts`:
- Around line 38-42: The guard currently uses truthiness (if (options.prompt))
which treats an explicit empty string as false and skips createAction; change it
to a presence check so any supplied --prompt (even "") triggers createAction:
replace the truthy test with a property-existence check such as if
(Object.prototype.hasOwnProperty.call(options, 'prompt')) or if ('prompt' in
options) so that createAction (imported from create/create-action.js) is always
invoked when the flag is provided and prompt validation (validatePrompt) can run
instead of falling back to sitesCreate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c59889ac-2c43-4e94-ae4e-c10221686aee

📥 Commits

Reviewing files that changed from the base of the PR and between b6ae4f5 and 4114665.

📒 Files selected for processing (2)
  • docs/commands/sites.md
  • src/commands/sites/sites.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/commands/sites.md

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
tests/integration/commands/create/create.test.ts (1)

381-387: ⚠️ Potential issue | 🟡 Minor

Remove the remaining non-null assertions in the request assertions.

toBeDefined() does not narrow the find() result here, so these three ! usages still violate @typescript-eslint/no-non-null-assertion and keep lint red. Add an explicit guard, or switch to optional chaining in the assertion.

🔧 Example fix
           const siteCreateRequest = requests.find(
             (r) => r.path === '/api/v1/test-account/sites' && r.method === 'POST',
           )
           expect(siteCreateRequest).toBeDefined()
-          expect(siteCreateRequest!.body).toEqual(
+          if (!siteCreateRequest) {
+            throw new Error('expected site create request')
+          }
+          expect(siteCreateRequest.body).toEqual(
             expect.objectContaining({ name: 'my-cool-site', created_via: 'agent_runner' }),
           )

Apply the same guard pattern to the later siteCreateRequest block and the agentRunnerRequest block.

Also applies to: 581-587, 608-618

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/commands/create/create.test.ts` around lines 381 - 387, The
assertions use non-null assertions on the results of requests.find
(siteCreateRequest and agentRunnerRequest), which violates the
no-non-null-assertion lint rule; replace the `!` uses by adding an explicit
guard (e.g., expect(siteCreateRequest).toBeDefined() followed by a conditional
branch that runs the body assertion) or switch to optional chaining in the
expectation (e.g., expect(siteCreateRequest?.body).toEqual(...)); apply the same
fix to the other occurrences (the later siteCreateRequest and agentRunnerRequest
blocks) so no `!` remains.
🧹 Nitpick comments (1)
src/commands/create/create-action.ts (1)

117-117: Prefer helper extraction over step-by-step narration comments.

These headers mostly restate the next block. Pulling the flow into helpers like resolvePrompt, resolveAgent, createSiteWithRetry, and downloadSource would keep the file self-describing without comment drift.

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

Also applies to: 138-138, 158-158, 183-183, 223-223, 265-265, 304-304, 335-335, 372-372

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/create/create-action.ts` at line 117, The file contains several
step-describing comments (e.g., "// Resolve prompt" and similar at lines noted)
that merely restate the following code; replace these comment blocks by
extracting the underlying logic into self-descriptive helper functions such as
resolvePrompt, resolveAgent, createSiteWithRetry, and downloadSource, and invoke
those helpers from create-action.ts so the flow is expressed by function names
rather than narration. Locate the code currently under each comment (prompt
resolution, agent resolution, site creation retry logic, source download, etc.),
move that logic into new exported/internal functions with the suggested names,
update the main flow to call these helpers, remove the redundant comments, and
ensure types and tests (if present) are updated to compile and preserve existing
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/create/create-action.ts`:
- Around line 351-365: The code currently treats any readdir() failure as
"missing directory" and unconditionally removes or advertises a local project;
update the create action (around projectDir handling in create-action.ts) to (1)
explicitly detect ENOENT vs other fs errors when calling fs.readdir or similar,
(2) introduce boolean flags (e.g., didCreateProjectDir and
didLinkOrDownloadProject) that are set only when this invocation actually
created the directory or performed the link/download, and (3) gate the recursive
rm(projectDir) cleanup and the local-next-steps/CD/deploy guidance on those
flags so you only remove or advertise projects this command created/linked;
apply the same guards to the other cleanup/advertise code paths mentioned (the
blocks currently around the later cleanup and guidance sections).
- Around line 351-365: The JSON and human-readable branches currently always
return success even when the agent runner finished with failure states; update
the code paths that handle the final agentRunner result (the block that calls
logJson(...) when options.json is true and the equivalent human-readable logging
path) to detect failing states (agentRunner.state === 'error' ||
agentRunner.state === 'cancelled') and then set a non-zero exit (e.g.,
process.exitCode = 1) or throw an Error after emitting the JSON/console payload
so shell callers observe failure; ensure you reference the same agentRunner,
agentRunUrl, site and logJson usage so the failure signal is emitted both for
--json and non-JSON branches.
- Around line 208-218: The code currently retries on any 422 from
createSiteInTeam and then reports "already taken"; change the retry logic to
only retry when the API error explicitly indicates a name-uniqueness collision.
Inside the error handling around createSiteInTeam (the block using error_,
MAX_NAME_RETRIES, nameAttempt, retries), replace the blanket (error_ as
APIError).status === 422 check with a check that inspects the APIError payload
for the uniqueness indicator (e.g., error_.body or
error_.body?.errors[0]?.message/code that denotes "name taken" or "unique
constraint"); only when that uniqueness marker is present and retries <
MAX_NAME_RETRIES do the random-suffix retry and continue. For other 422s, call
stopSpinner({ spinner: siteSpinner, error: true }) and return logAndThrowError
with the original API error message (don’t rewrite it to “already taken”). Also
ensure the later 422 branch that returns the "already taken" message is only
used when the same uniqueness marker is matched for the final failure.
- Around line 96-101: The PowerShell command currently interpolates tmpFile and
projectDir into the -Command string which breaks for paths containing single
quotes; update the execFile calls (the one in create-action.ts where
execFile('powershell.exe', [...]) runs Expand-Archive and the similar call in
src/lib/exec-fetcher.ts) to pass paths as parameters instead of string
interpolation: build a small PowerShell script string like "param($src,$dst)
Expand-Archive -Force -LiteralPath $src -DestinationPath $dst" and pass tmpFile
and projectDir via PowerShell's -ArgumentList (or use -File with a temp script)
so the paths are supplied as separate arguments and not embedded in the command
string, ensuring safe handling of quotes and special characters.

---

Duplicate comments:
In `@tests/integration/commands/create/create.test.ts`:
- Around line 381-387: The assertions use non-null assertions on the results of
requests.find (siteCreateRequest and agentRunnerRequest), which violates the
no-non-null-assertion lint rule; replace the `!` uses by adding an explicit
guard (e.g., expect(siteCreateRequest).toBeDefined() followed by a conditional
branch that runs the body assertion) or switch to optional chaining in the
expectation (e.g., expect(siteCreateRequest?.body).toEqual(...)); apply the same
fix to the other occurrences (the later siteCreateRequest and agentRunnerRequest
blocks) so no `!` remains.

---

Nitpick comments:
In `@src/commands/create/create-action.ts`:
- Line 117: The file contains several step-describing comments (e.g., "//
Resolve prompt" and similar at lines noted) that merely restate the following
code; replace these comment blocks by extracting the underlying logic into
self-descriptive helper functions such as resolvePrompt, resolveAgent,
createSiteWithRetry, and downloadSource, and invoke those helpers from
create-action.ts so the flow is expressed by function names rather than
narration. Locate the code currently under each comment (prompt resolution,
agent resolution, site creation retry logic, source download, etc.), move that
logic into new exported/internal functions with the suggested names, update the
main flow to call these helpers, remove the redundant comments, and ensure types
and tests (if present) are updated to compile and preserve existing behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e5b72d8e-6147-47bf-b21b-7dc5804ddfdb

📥 Commits

Reviewing files that changed from the base of the PR and between 4114665 and a16b63b.

📒 Files selected for processing (5)
  • docs/commands/create.md
  • src/commands/agents/types.ts
  • src/commands/create/create-action.ts
  • src/commands/create/create.ts
  • tests/integration/commands/create/create.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/commands/agents/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/create/create.ts

VitaliyR
VitaliyR previously approved these changes Mar 19, 2026
Copy link
Contributor

@VitaliyR VitaliyR left a comment

Choose a reason for hiding this comment

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

Nothing suspicious on my end, although give a chance to somebody with more CLI knowledge review it as well

try {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
while (true) {
await sleep(POLL_INTERVAL)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: well, we are using the p-wait-for as far as I can tell
Like here

export const waitForDiff = async (api, deployId, siteId, timeout) => {

Not critical for sure, just letting you know

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