Skip to content

refactor(onboard): add FSM runner shell#4453

Merged
cv merged 18 commits into
mainfrom
stack/onboard-fsm-runner
Jun 4, 2026
Merged

refactor(onboard): add FSM runner shell#4453
cv merged 18 commits into
mainfrom
stack/onboard-fsm-runner

Conversation

@cv

@cv cv commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add a reusable onboarding FSM runner shell that dispatches handlers until the machine reaches a terminal state. The runner applies handler results through OnboardRuntime, supporting advance, retry, branch, complete, and failure paths without wiring it into live onboarding yet.

Changes

  • Add src/lib/onboard/machine/runner.ts with handler dispatch, context update, terminal-state, and missing-handler support.
  • Add runner tests covering a full path with retry and branch transitions.
  • Add tests for terminal failure and missing non-terminal handlers.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

  • npx prek run --all-files passes
  • npm test passes
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Carlos Villela cvillela@nvidia.com

Summary by CodeRabbit

  • New Features
    • Introduced an onboarding state machine runner with transition management, retry and branching support, and configurable transition limits.
  • Tests
    • Added comprehensive test coverage validating sequential execution, state handling, error propagation, and transition limit enforcement.

cv added 15 commits May 27, 2026 15:18
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@cv cv self-assigned this May 28, 2026
@copy-pr-bot

copy-pr-bot Bot commented May 28, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds runOnboardMachine, a new orchestration function that drives onboarding state machines by repeatedly fetching session state, dispatching to state handlers, applying transition results via runtime, optionally updating context, and terminating on terminal state or transition limits. Includes complete test coverage for success flows, terminal failures, early returns, error propagation, missing handlers, and limit enforcement.

Changes

Onboarding State Machine Runner

Layer / File(s) Summary
State handler contracts and runner options
src/lib/onboard/machine/runner.ts
Public TypeScript API defines OnboardStateHandler type for per-state functions, OnboardStateHandlers map type, OnboardMachineRunnerRuntime interface for session/context mutation access, OnboardMachineRunnerOptions with handlers, runtime, context update hook, and transition limits, and OnboardMachineRunnerResult holding final session and context.
Error classes and transition-limit normalization
src/lib/onboard/machine/runner.ts
Exported error classes MissingOnboardStateHandlerError (thrown when a non-terminal state lacks a handler) and OnboardMachineTransitionLimitError (thrown when transitions exceed cap). Internal normalizeMaxTransitions helper enforces a bounded positive integer default and ceiling.
Main runner orchestration loop
src/lib/onboard/machine/runner.ts
runOnboardMachine async function repeatedly fetches current state from session, selects and invokes the corresponding handler, applies the returned OnboardStateResult through runtime, calls optional updateContext hook, and continues until reaching a terminal state or exceeding the normalized transition limit. Propagates runtime errors without context updates and throws when handlers or transition limits fail.
Comprehensive test coverage for runner behavior
src/lib/onboard/machine/runner.test.ts
Vitest suite with in-test helpers (RunnerContext type, session cloning, OnboardRuntime factory) validating that runners execute handlers sequentially, apply retry/branch transitions and context updates correctly, stop on terminal failure without invoking further handlers, return immediately for pre-terminal sessions, propagate runtime errors, throw MissingOnboardStateHandlerError for missing handlers, and throw OnboardMachineTransitionLimitError when transitions exceed the configured cap.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • NVIDIA/NemoClaw#4376: The main PR's new runOnboardMachine runner executes handlers to produce OnboardStateResult and then applies those explicit transition results via the runtime, which is exactly what PR #4376 added/changed in src/lib/onboard/machine/runtime.ts (notably applyResult/complete metadata and transition-kind validation).

  • NVIDIA/NemoClaw#4446: The main PR adds the runOnboardMachine runner that consumes each state handler's returned FSM transition (stateResult/transition result) to advance/branch the machine, and the retrieved PR updates the preflight and gateway handlers to start returning those explicit stateResult transitions—so they directly align at the handler→runner transition-contract level.

  • NVIDIA/NemoClaw#4443: Main PR introduces runOnboardMachine to iterate handlers and apply returned OnboardStateResults via the runtime, which directly aligns with the retrieved PR's refactor of finalization to return stateResult and the addition of recordStateResult to apply those results through the runtime boundary.

Suggested reviewers

  • prekshivyas
  • cjagwani
  • ericksoa

Poem

🐰 A runner hops through state by state,
Each handler returns what comes next straight,
Through retries and branches the machine will dance,
Till terminal states conclude the advance!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor(onboard): add FSM runner shell' clearly and specifically summarizes the main change: adding a new finite state machine runner for the onboarding system.
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 stack/onboard-fsm-runner

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

@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor

Findings: 0 needs attention, 2 worth checking, 0 nice ideas
Since last review: 0 prior items resolved, 1 still applies, 0 new items found

Review findings

🛠️ Needs attention

  • None.

🔎 Worth checking

  • Source-of-truth review needed: normalizeMaxTransitions in src/lib/onboard/machine/runner.ts: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: normalizeMaxTransitions returns Math.max(1, Math.trunc(value)); comparisons against NaN fail open and Infinity is unbounded.
  • Validate non-finite maxTransitions (src/lib/onboard/machine/runner.ts:66): The runner adds maxTransitions as the safety valve for retry-capable handlers, but normalizeMaxTransitions does not reject or clamp non-finite values. With Number.NaN, the loop check transitions >= transitionLimit is always false; with Infinity, the cap is effectively unbounded. There is no evidence this option is user-controlled today, but this guard is intended to prevent unbounded onboarding FSM execution before the runner is wired into live onboarding.
    • Recommendation: Use Number.isFinite(value) before truncating and either reject invalid values or clamp them to a finite default. Add negative tests for Number.NaN and Infinity so the transition cap cannot be accidentally disabled.
    • Evidence: normalizeMaxTransitions returns Math.max(1, Math.trunc(value)); runOnboardMachine checks if (transitions >= transitionLimit) before dispatching handlers. Math.trunc(Number.NaN) yields NaN, making the comparison non-enforcing, and Infinity remains non-finite.

🌱 Nice ideas

  • None.
Consider writing more tests for
  • **Mocked behavioral coverage** — runOnboardMachine rejects or clamps maxTransitions: Number.NaN instead of disabling the transition limit. The runner is a control-flow boundary over OnboardRuntime state transitions. Existing tests appropriately use in-memory mocked runtime dependencies while exercising real OnboardRuntime validation. Additional negative tests are advisable for the transition-limit guard.
  • **Mocked behavioral coverage** — runOnboardMachine rejects or clamps maxTransitions: Infinity instead of allowing an unbounded retry loop. The runner is a control-flow boundary over OnboardRuntime state transitions. Existing tests appropriately use in-memory mocked runtime dependencies while exercising real OnboardRuntime validation. Additional negative tests are advisable for the transition-limit guard.
  • **Mocked behavioral coverage** — runOnboardMachine returns immediately for an initially failed terminal session without invoking handlers. The runner is a control-flow boundary over OnboardRuntime state transitions. Existing tests appropriately use in-memory mocked runtime dependencies while exercising real OnboardRuntime validation. Additional negative tests are advisable for the transition-limit guard.
  • **Acceptance clause:** `npx prek run --all-files` passes — add test evidence or identify existing coverage. This is a PR-body verification claim. Review used read-only inspection and did not execute commands.
  • **Acceptance clause:** `npm test` passes — add test evidence or identify existing coverage. This is a PR-body verification claim. Review used read-only inspection and did not execute commands.
  • **normalizeMaxTransitions in src/lib/onboard/machine/runner.ts** — Missing tests should prove Number.NaN and Infinity are rejected or clamped instead of disabling the transition cap.. normalizeMaxTransitions returns Math.max(1, Math.trunc(value)); comparisons against NaN fail open and Infinity is unbounded.
Since last review details

Current findings:

  • Source-of-truth review needed: normalizeMaxTransitions in src/lib/onboard/machine/runner.ts: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: normalizeMaxTransitions returns Math.max(1, Math.trunc(value)); comparisons against NaN fail open and Infinity is unbounded.
  • Validate non-finite maxTransitions (src/lib/onboard/machine/runner.ts:66): The runner adds maxTransitions as the safety valve for retry-capable handlers, but normalizeMaxTransitions does not reject or clamp non-finite values. With Number.NaN, the loop check transitions >= transitionLimit is always false; with Infinity, the cap is effectively unbounded. There is no evidence this option is user-controlled today, but this guard is intended to prevent unbounded onboarding FSM execution before the runner is wired into live onboarding.
    • Recommendation: Use Number.isFinite(value) before truncating and either reject invalid values or clamp them to a finite default. Add negative tests for Number.NaN and Infinity so the transition cap cannot be accidentally disabled.
    • Evidence: normalizeMaxTransitions returns Math.max(1, Math.trunc(value)); runOnboardMachine checks if (transitions >= transitionLimit) before dispatching handlers. Math.trunc(Number.NaN) yields NaN, making the comparison non-enforcing, and Infinity remains non-finite.

Workflow run details

This is an automated advisory review. A human maintainer must make the final merge decision.

@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: ubuntu-repo-cloud-openclaw, ubuntu-repo-cloud-openclaw-resume
Optional E2E: ubuntu-repo-cloud-openclaw-repair, ubuntu-no-docker-preflight-negative

Dispatch hint: ubuntu-repo-cloud-openclaw,ubuntu-repo-cloud-openclaw-resume

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • ubuntu-repo-cloud-openclaw (medium): Validates the primary real user onboarding path from repo checkout through Docker/OpenShell sandbox creation, OpenClaw setup, smoke checks, cloud inference, and credential assertions. This is the minimum required E2E for changes under the onboarding machine runtime.
  • ubuntu-repo-cloud-openclaw-resume (medium): Exercises interrupted/resumed onboarding behavior, which is directly adjacent to the runner's terminal-session handling and retry/transition loop behavior.

Optional E2E

  • ubuntu-repo-cloud-openclaw-repair (medium): Additional confidence for resume repair and invalidation edge cases when onboarding state exists but sandbox/config state diverges. Useful because the runner changes state-machine execution semantics, but less directly touched than the happy path and resume path.
  • ubuntu-no-docker-preflight-negative (low): Checks negative preflight termination without sandbox side effects. Useful because the runner handles failed terminal results and must stop execution after failures.

New E2E recommendations

  • None.

Dispatch hint

  • Workflow: .github/workflows/e2e-scenarios.yaml
  • jobs input: ubuntu-repo-cloud-openclaw,ubuntu-repo-cloud-openclaw-resume

@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

E2E Scenario Advisor Recommendation

Required scenario E2E: ubuntu-repo-cloud-openclaw, ubuntu-no-docker-preflight-negative, ubuntu-repo-cloud-openclaw-double-provider-switch
Optional scenario E2E: ubuntu-repo-cloud-hermes, ubuntu-repo-cloud-openclaw-resume, wsl-repo-cloud-openclaw, macos-repo-cloud-openclaw

Dispatch required scenario E2E:

  • gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw
  • gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-no-docker-preflight-negative
  • gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-double-provider-switch

Workflow run

Full scenario advisor summary

E2E Scenario Advisor

Base: origin/main
Head: HEAD
Confidence: medium

Required scenario E2E

  • ubuntu-repo-cloud-openclaw: Production onboarding state-machine runner code changed. The primary Ubuntu repo-current cloud OpenClaw scenario exercises the standard successful onboarding path, sandbox startup, gateway health, inference, credentials, and baseline onboarding assertions.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw
  • ubuntu-no-docker-preflight-negative: The runner change includes terminal failure behavior. This dispatchable negative scenario is the smallest routed path that validates onboarding stops at the expected preflight failure without sandbox side effects.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-no-docker-preflight-negative
  • ubuntu-repo-cloud-openclaw-double-provider-switch: The runner change includes retry/branch-style transition handling. The double-provider-switch onboarding profile is the targeted routed Ubuntu scenario most likely to exercise non-linear provider-selection onboarding behavior while avoiding special runners.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-double-provider-switch

Optional scenario E2E

  • ubuntu-repo-cloud-hermes: Optional adjacent coverage for the same onboarding machinery with the Hermes agent branch instead of OpenClaw.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-hermes
  • ubuntu-repo-cloud-openclaw-resume: Optional lifecycle coverage for resume-after-interrupt behavior, relevant to runner handling of previously entered states and resumable sessions.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-resume
  • wsl-repo-cloud-openclaw: Optional platform-adjacent coverage of the same cloud OpenClaw onboarding path under WSL; special-runner placement makes it non-primary.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=wsl-repo-cloud-openclaw
  • macos-repo-cloud-openclaw: Optional platform-adjacent CLI/onboarding coverage on macOS; Docker-dependent suites are skipped on hosted macOS, so this is not a primary validation path.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=macos-repo-cloud-openclaw

Relevant changed files

  • src/lib/onboard/machine/runner.ts

@wscurran wscurran added enhancement: feature refactor PR restructures code without intended behavior change labels May 28, 2026
@cv cv added the onboarding label May 29, 2026
@wscurran wscurran added area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow feature PR adds or expands user-visible functionality and removed enhancement: feature labels Jun 3, 2026
Base automatically changed from stack/onboard-fsm-provider-inference-result to main June 4, 2026 20:52
@cv cv marked this pull request as ready for review June 4, 2026 20:52
cv added 3 commits June 4, 2026 14:05
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Selective E2E Results — ✅ All requested jobs passed

Run: 26979740935
Target ref: 796ed7be16f14d097bc3a87544072c5092aeb4f2
Workflow ref: main
Requested jobs: cloud-onboard-e2e,onboard-resume-e2e
Summary: 2 passed, 0 failed, 0 skipped

Job Result
cloud-onboard-e2e ✅ success
onboard-resume-e2e ✅ success

@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: 2

🤖 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/lib/onboard/machine/runner.test.ts`:
- Around line 35-38: updateSession currently discards in-place mutations because
it calls mutator(cloneSession(session)) and if the mutator returns void it falls
back to the original session; change it to pass a cloned mutable object to
mutator, capture that mutated object, and use it when mutator returns void: call
const mutated = cloneSession(session); const result = mutator(mutated); const
next = result ?? mutated; then set session = cloneSession(next) and return
cloneSession(session). This preserves in-place mutations while still supporting
mutator returns of Session or void for the updateSession helper.

In `@src/lib/onboard/machine/runner.ts`:
- Around line 66-69: The normalizeMaxTransitions function currently passes
Infinity/NaN through Math.trunc/Math.max which can produce Infinity or NaN and
disable the safety check (transitions >= transitionLimit); update
normalizeMaxTransitions to treat any non-finite input as undefined by first
checking Number.isFinite(value) (and returning DEFAULT_MAX_TRANSITIONS if not
finite), then apply Math.trunc and Math.max(1, ...) so the returned
transitionLimit is always a finite integer >= 1; ensure callers that compare
transitions >= transitionLimit (the safety valve) rely on this normalized value.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 894be915-0e81-42ad-80b5-75936722f84f

📥 Commits

Reviewing files that changed from the base of the PR and between 5c54e41 and 3e4fcf7.

📒 Files selected for processing (2)
  • src/lib/onboard/machine/runner.test.ts
  • src/lib/onboard/machine/runner.ts

Comment on lines +35 to +38
const updateSession = (mutator: (value: Session) => Session | void): Session => {
const next = mutator(cloneSession(session)) ?? session;
session = cloneSession(next);
return cloneSession(session);

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 | ⚡ Quick win

updateSession drops in-place mutations when mutator returns void.

On Line 36, fallback to session discards mutations made to the cloned argument when mutator returns void. That makes the test double less faithful to the declared (Session | void) contract.

Suggested fix
 const updateSession = (mutator: (value: Session) => Session | void): Session => {
-  const next = mutator(cloneSession(session)) ?? session;
+  const current = cloneSession(session);
+  const next = mutator(current) ?? current;
   session = cloneSession(next);
   return cloneSession(session);
 };
🤖 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/lib/onboard/machine/runner.test.ts` around lines 35 - 38, updateSession
currently discards in-place mutations because it calls
mutator(cloneSession(session)) and if the mutator returns void it falls back to
the original session; change it to pass a cloned mutable object to mutator,
capture that mutated object, and use it when mutator returns void: call const
mutated = cloneSession(session); const result = mutator(mutated); const next =
result ?? mutated; then set session = cloneSession(next) and return
cloneSession(session). This preserves in-place mutations while still supporting
mutator returns of Session or void for the updateSession helper.

Comment on lines +66 to +69
function normalizeMaxTransitions(value: number | undefined): number {
if (value === undefined) return DEFAULT_MAX_TRANSITIONS;
return Math.max(1, Math.trunc(value));
}

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

Harden transition-limit normalization against non-finite values.

At Line 66-69, Math.trunc + Math.max allows Infinity and yields NaN for NaN; either case can break the safety valve used at Line 84-85 (transitions >= transitionLimit) and effectively remove the cap.

Proposed fix
 const DEFAULT_MAX_TRANSITIONS = 100;
+const MAX_TRANSITIONS_CAP = 10_000;
 
 function normalizeMaxTransitions(value: number | undefined): number {
   if (value === undefined) return DEFAULT_MAX_TRANSITIONS;
-  return Math.max(1, Math.trunc(value));
+  if (!Number.isFinite(value)) return DEFAULT_MAX_TRANSITIONS;
+  return Math.min(MAX_TRANSITIONS_CAP, Math.max(1, Math.trunc(value)));
 }

Also applies to: 81-85

🤖 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/lib/onboard/machine/runner.ts` around lines 66 - 69, The
normalizeMaxTransitions function currently passes Infinity/NaN through
Math.trunc/Math.max which can produce Infinity or NaN and disable the safety
check (transitions >= transitionLimit); update normalizeMaxTransitions to treat
any non-finite input as undefined by first checking Number.isFinite(value) (and
returning DEFAULT_MAX_TRANSITIONS if not finite), then apply Math.trunc and
Math.max(1, ...) so the returned transitionLimit is always a finite integer >=
1; ensure callers that compare transitions >= transitionLimit (the safety valve)
rely on this normalized value.

@cv cv merged commit 93adbc7 into main Jun 4, 2026
152 of 156 checks passed
@cv cv deleted the stack/onboard-fsm-runner branch June 4, 2026 21:25
@wscurran wscurran removed the feature PR adds or expands user-visible functionality label Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow refactor PR restructures code without intended behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants