Skip to content

fix(cloud): exit non-zero when the CloudFormation connect is still pending - #79

Merged
justinhelmer merged 1 commit into
mainfrom
fix/cloud-connect-pending-exit-code
Aug 26, 2026
Merged

fix(cloud): exit non-zero when the CloudFormation connect is still pending#79
justinhelmer merged 1 commit into
mainfrom
fix/cloud-connect-pending-exit-code

Conversation

@justinhelmer

@justinhelmer justinhelmer commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

When polylane cloud connect --provider aws ends while the CloudFormation stack is still creating, the CLI now exits with a new code 7 (PENDING) instead of 0. Scripts and the installer funnel can no longer mistake an unfinished AWS connect for a completed one.

What & why

Fixes cli#78, the follow-up from the review of cli#77 (which fixed cli#75); tracked as the cli#78 row on nominal#1511.

The AWS leg of cloud connect hands the CloudFormation deploy to a background poll and settles it with a bounded foreground wait when the session ends. If the account has not shown up by then, AwsStackWait.settle() returns 'pending', prints "AWS is still connecting", and the command returned without touching process.exitCode. Since #77 made main.ts exit with settledExitCode(), that path was the last connect outcome still exiting 0 while nothing was connected.

Also fixes cli#80: the live spec changed the AWS connect body to regions: string[] | null (nominal#1376), which broke npm run typecheck on main with no code change and kept CI red on this PR.

Changes

Exit code:

  • src/errors/codes.ts: ExitCode.PENDING = 7.
  • src/commands/cloud/connect.ts: ConnectOutcome splits into HandoffOutcome (connected | timeout, what a provider flow returns) plus pending (only AwsStackWait.settle() produces it), so the types say which path can carry which value. connectExitCode(outcome, awsOutcome) maps the settled outcomes to an exit code (timeout → 1, any pending → 7, else 0). Both exits of the picker loop (a provider flow finished; the user picked Done with AWS still deploying) now set process.exitCode through it, so settledExitCode() in main.ts carries it to the process boundary and telemetry. The "still connecting" message now says the account appears in polylane cloud list once the stack finishes and names the exit code.
  • ERRORS.md, README.md, skill/SKILL.md: code 7 added to every exit-code table; ERRORS.md gets a "Pending (exit 7)" category and the browser-flow paragraph now states what the exit code reflects after a wait (0 / 1 / 7) instead of the pre-fix: exit with the code a command set on process.exitCode instead of a hard 0 #77 "URL generation only" wording.
  • test/exit-code.test.ts: unit cases for connectExitCode, plus the same subprocess shape as #77 asserting main.ts exits 7 when a command leaves process.exitCode = 7. test/cloud-connect-aws-background.test.ts: the pending guidance names the exit code.

AWS regions:

  • src/commands/cloud/connect.ts: parseAwsRegions() turns --region into the API's regions list: comma-separated values, or allnull (every region enabled on the account, including regions enabled later, the same semantics as the console's "All regions" switch). Mixing all with specific regions is a usage error rather than a silent widen. The interactive picker offers "All regions" first. Flag help and examples updated.
  • test/cloud-connect-aws-regions.test.ts: single, list, all, mixed all, and empty-value cases.

Decisions

  • New code 7 rather than reusing 1 or 5. A pending stack is not an error and not a request timeout: the launch succeeded and the account will arrive on its own. Reusing GENERAL would make "retry the connect" and "just wait" indistinguishable to callers; TIMEOUT (5) is the HTTP request-timeout category and would mislead the retry hints in ERRORS.md. The semantics are documented as "not connected yet, re-check with list".
  • Timeout outranks pending. When a browser-wait timeout and a pending AWS stack happen in one session, the process exits 1: the caller has something to redo, and 7 would tell them to just wait.
  • Exit code set at the command boundary, not inside settle(). startAwsStackWait stays a pure wait primitive that the existing unit tests drive; the mapping lives in one function beside ConnectOutcome where both outcomes meet.
  • Regions fix bundled here. It is a separate cause (spec drift, #80) but CI cannot go green without it, and a stacked PR would only serialize the merge.
  • --region all instead of a new --all-regions flag. Keeps one flag for the region choice, works for scripts that already pass --region, and mirrors the picker's first entry. --region us-east-1 keeps working unchanged (becomes ["us-east-1"]). The interactive picker stays single-select (one region, or All regions); a specific multi-region set is reachable through the flag only. That is a conscious scope call: the picker is for the guided path, scripts compose lists.

Validation

# Criterion Receipt
1 connectExitCode returns 0 for connected legs, 7 when either outcome is pending, 1 when any wait timed out (including timeout + pending) test/exit-code.test.ts "cloud connect exit code" ✅ (red before: does not provide an export named 'connectExitCode')
2 A completed command with process.exitCode = 7 exits the process with 7 subprocess case "exits with the pending code" ✅
3 Pending guidance still tells the user how to check and what to do if the stack failed cloud-connect-aws-background.test.ts asserts polylane cloud list, failed or rolled back, Exiting 7 (pending)
4 Every exit-code table documents 7 ERRORS.md / README.md / skill/SKILL.md rows
5 --region maps single, comma-separated, and all values; rejects empty and all-mixed-with-regions values with exit 2 test/cloud-connect-aws-regions.test.ts 5 cases ✅ (red before: missing export; the mixed case returned null before the check existed)
6 Suite / typecheck / lint / build as CI runs them npm run test 377/377 ✅ · npm run typecheck ✅ (was failing on main, #80) · npm run lint ✅ · npm run build + ./dist/polylane.mjs cloud connect --help shows the new --region help ✅
7 Human-gated, after release: real AWS connect, pick Done before the stack finishes → echo $? prints 7, then polylane cloud list shows the account once created; --region all connect scans every enabled region ⏳ next fresh-install pass on nominal#1511

🤖 Generated with Claude Code

@justinhelmer

Copy link
Copy Markdown
Contributor Author

CI note: the Test & typecheck jobs fail on npm run typecheck with connect.ts: 'region' does not exist ... Did you mean 'regions'. That is live-spec drift on main (the AWS connect body changed to regions: string[] | null), filed as #80; this diff does not touch that line and the same failure reproduces on main. npm run lint, npm run test (372/372) and npm run build pass here.

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

LGTM: correctly closes the cli#78 gap — the AWS pending outcome now sets process.exitCode at both exits of the connect picker loop, exit code 7 doesn't collide with anything else in src/errors/codes.ts, and no in-repo caller depended on the old silent-0 behavior (grepped for cloud connect invocations in scripts/workflows/package.json — none found; the only dependent, the installer, is out-of-repo and per #77 no longer relies on this exit code anyway).

Verified independently (not just the PR body's claims):

  • Head SHA 9b4ab3e48b7dab3a137107daf5d0b3c11897b98b checked out and confirmed byte-for-byte against the PR API's head.sha. True merge-base (git merge-base HEAD origin/main) is 9aac699..., exactly matching the PR's stated base.sha — not stale. git diff --stat against that merge-base is 8 files changed, 111 insertions(+), 17 deletions(-), matching the API's additions/deletions/changed_files exactly.
  • Traced both connectExitCode call sites (lines ~903, ~909 in the picker loop) by hand against ConnectOutcome/AwsStackWait.settle()'s actual return values — the mapping (timeout→1, any pending→7, else 0, with timeout outranking pending) is correct and matches the stated decisions.
  • Exit code 7 is documented in ERRORS.md (new "Pending" category + updated browser-flow paragraph), README.md, and skill/SKILL.md — all three exit-code tables updated consistently.
  • Test coverage: connectExitCode unit tests cover all three branches including the timeout-outranks-pending case; test/exit-code.test.ts's subprocess case spawns a real node --import tsx src/main.ts config show (same shape as #77) and asserts the process actually exits 7 — a genuine process-boundary test, not just a helper unit test; startAwsStackWait's existing subprocess-adjacent test now also asserts the updated "Exiting 7 (pending)" message text. parseAwsRegions gets 4 direct-call cases (single/comma-list/all/empty).
  • Local checks at this head: npm run lint — 0 errors. npm run test — 376/376 pass (confirmed on a repeat run; an initial run immediately after git checkout undercounted at 371/89 suites, which reproduced as a local filesystem-timing artifact right after checkout, not a real failure — a second run picked up all 90 suites cleanly). npm run typecheckpasses at this head (0 errors); reproduced the previously-reported failure independently by running the identical npm run typecheck on a clean origin/main worktree, which fails with the exact same TS2561: ... 'region' does not exist ... Did you mean 'regions'? at (the base branch's) line 552 — confirming it was real, pre-existing spec drift (cli#80) unrelated to this diff's first commit, now fixed by this PR's second commit.
  • CI at this exact head SHA: checks / Test & typecheck green on all of 20.x/22.x/24.x (confirmed via GET /commits/{sha}/check-runs, not a cached view). The earlier red run (first commit, SHA ea73213) was independently confirmed via the raw CI job log to fail at the identical connect.ts:563 region/regions error — matching cli#80 exactly and outside this diff's touched lines.

Scope note (non-blocking): the second commit does more than the minimal spec-drift fix — it also implements cli#80's suggested --region redesign (comma-separated list, all keyword, updated picker/help/examples, new parseAwsRegions + its test file). The PR body is transparent about why it's bundled (CI can't go green without it) and keeps it as a separate, atomic commit per AGENTS.md. Correct and tested, just flagging that this PR now does two distinct things under one exit-code-focused title/issue.

Two inline nits left on the diff, neither blocking.


Generated by Claude Code

Comment thread src/commands/cloud/connect.ts Outdated
Comment thread src/commands/cloud/connect.ts Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved: Claude reviewed this PR and posted an LGTM verdict (see its review). A repo admin enabled this via the auto-approve workflow.


Generated by Claude Code

…nding

cloud connect --provider aws could end with the stack still creating and exit 0, so scripts and the installer funnel read an unfinished connect as success. Add ExitCode.PENDING (7), route every connect outcome through connectExitCode(), and document the code (#78).

Also send the AWS connect body the API now expects: regions: string[] | null (null = every enabled region) replaced region: string and broke typecheck on main (#80). --region takes a comma-separated list or "all"; the picker offers All regions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@justinhelmer
justinhelmer force-pushed the fix/cloud-connect-pending-exit-code branch from 9b4ab3e to a978c13 Compare August 25, 2026 23:41

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

LGTM: re-review at the squashed head — supersedes my prior review at ea73213/9b4ab3e; substance unchanged, both nits now genuinely fixed.

Head verification: fetched the PR fresh; head is a978c1361d94392372972b734319fe32ee052b26 (matches Justin's a978c13, checked the full 40-char SHA rather than trusting the short form). Checked out pull/79/head and confirmed git rev-parse HEAD matches byte-for-byte. mergeable_state is now clean.

Merge-base / diff-stat: true merge-base (git merge-base HEAD origin/main) is still 9aac699..., exactly the PR's stated base.sha — not stale. git diff --stat against it: 8 files, +119/-21, matching the PR API's reported totals exactly.

Squash sanity check: diffed this head directly against the prior (unsquashed, already-reviewed) second-commit head 9b4ab3e... to see what actually changed content-wise, not just history. The only differences are the two nit fixes — nothing else moved:

  • connectExitCode's outcome param is now typed HandoffOutcome | null (HandoffOutcome = 'connected' | 'timeout', split out from ConnectOutcome), and the dead outcome === 'pending' branch is gone — a proper type-level fix, not just a comment. confirmBrowserConnect/browserConnect/connectProvider return types updated to match; the now-impossible connectExitCode('pending', null) test case was correctly removed.
  • parseAwsRegions now rejects all mixed with specific regions ("...,all"CLIError with a --region hint) instead of silently collapsing to "every region"; test/cloud-connect-aws-regions.test.ts gained the corresponding case. The interactive-picker-is-single-select gap is now called out explicitly as a conscious decision in the PR body's Decisions section rather than left implicit.
  • ERRORS.md, README.md, skill/SKILL.md, src/errors/codes.ts, test/cloud-connect-aws-background.test.ts are byte-identical to what I already reviewed. No new scope crept in during the squash.

Local checks at this head: npm run lint — 0 errors. npm run typecheck — 0 errors (still fixed). npm run test — 0 failures on every run, but the aggregate count was flaky in this sandbox (372/89 vs 377/90 across repeated runs) — traced this to a Node test-runner concurrency/reporting artifact reproducible on this same box even for the unchanged baseline suites, not a real gap: direct TAP inspection of a "372" run still showed the parseAwsRegions suite (all 5 cases, including the new mixed-region case) executing and passing. Settled it by pulling the actual CI job log for this head's 22.x run: # tests 377 / # suites 90 / # pass 377 / # fail 0, matching the PR body's claim exactly.

CI at this exact head: polled GET /commits/a978c13.../check-runs directly (not cached) — checks / Test & typecheck green on 20.x/22.x/24.x, plus the auto-approve workflow's approve runs showing skipped (expected pre-review).

No new findings. Nothing further to block on.


Generated by Claude Code

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved: Claude reviewed this PR and posted an LGTM verdict (see its review). A repo admin enabled this via the auto-approve workflow.


Generated by Claude Code

@justinhelmer
justinhelmer merged commit 26b12dd into main Aug 26, 2026
6 checks passed
@justinhelmer
justinhelmer deleted the fix/cloud-connect-pending-exit-code branch August 26, 2026 00:00
@justinhelmer

Copy link
Copy Markdown
Contributor Author

Prod receipt · polylane CLI 0.2.28

  • Merged 2026-08-26 00:00Z (26b12dde) → Cut Release: next patch ✅ → Release run ✅: checks 20/22/24 · Publish to npm + GitHub Release · Bump Homebrew formula · Real install smoke via polylane.com, all green.
  • GitHub release v0.2.28 published 00:02:56Z with polylane.mjs + checksums.txt; downloaded asset sha256 218eee06e801… matches checksums.txt.
  • npm @coreplane/polylane@0.2.28 resolves as latest.
  • Fix present in the shipped bundle: ExitCode.PENDING=7 is in the published polylane.mjs.

Still human-gated (needs a real AWS account): a live cloud connect where Done is picked before the stack finishes exits 7; --region all scans every enabled region. Tracked on the #1511 fresh-install pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant