Skip to content

fix(onboard): fall back on a negative timeout or poll override - #7891

Merged
prekshivyas merged 4 commits into
mainfrom
fix/envint-rejects-negative-7881
Aug 1, 2026
Merged

fix(onboard): fall back on a negative timeout or poll override#7891
prekshivyas merged 4 commits into
mainfrom
fix/envint-rejects-negative-7881

Conversation

@yanyunl1991

@yanyunl1991 yanyunl1991 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

envInt clamped a negative override to 0 while falling back to the caller's default for
every other unusable value. Zero is the most damaging reading available for the knobs it
backs — an empty poll loop, a zero-second readiness budget, a timeout that expires before
its first attempt. This makes a negative fall back like any other invalid input.

Closes #7881.

Scope

#7881 has been narrowed to this single defect. Two neighbouring observations that surfaced while
resolving the call sites are behaviour changes to sandbox recovery timing rather than input
validation, and are tracked separately in #7893 so they can be verified against a real cold start
without holding up a zero-risk validation fix:

  • the recreate readiness-wait default GATEWAY_RECOVERY_WAIT_DEFAULT_SECONDS is bypassed by an
    explicit timeoutSeconds at four of its five production call sites;
  • NEMOCLAW_GATEWAY_RECOVERY_WAIT_SECONDS has opposite precedence in the two functions that read
    it.

Neither is touched here. This PR changes one function in src/lib/onboard/env.ts.

Analysis

src/lib/onboard/env.ts:

const n = Number(raw);
return Number.isFinite(n) ? Math.max(0, Math.round(n)) : fallback;

Observed behaviour, next to the sibling helper readNonNegativeNumberEnv
(src/lib/actions/sandbox/process-recovery.ts):

override envInt readNonNegativeNumberEnv
-1 0 30 (fallback)
abc 30 (fallback) 30 (fallback)
0 0 0

abc and -1 are both unusable, but only one reaches the default. The other becomes 0,
and 0 is exactly the value these knobs cannot survive:

  • NEMOCLAW_HEALTH_POLL_COUNT=0 → zero polls, so readiness gives up before it looks once.
  • NEMOCLAW_HEALTH_POLL_INTERVAL=0 → no sleep between polls.
  • NEMOCLAW_GATEWAY_START_TIMEOUT=00 * 1000, a timeout already expired at its first check.
  • NEMOCLAW_SANDBOX_READY_TIMEOUT=0 → a zero-second readiness budget.

The existing test suite already contained the inconsistency side by side:
sandbox-readiness-tracing.test.ts asserted "abc"30 and, twelve lines later, "-5"
1 — the latter only because that one caller wraps the result in a local clamp. That local
clamp is the second symptom: src/lib/onboard/docker-gpu-supervisor-reconnect.ts guards its
own call with Math.max(1, envInt(...)), and the other ~20 call sites do not.

Fix

Reject a negative the way every other unusable value is already rejected, matching what the
sibling helper has always done:

if (!Number.isFinite(n) || n < 0) return fallback;
return Math.round(n);

An explicit 0 is deliberately unchanged. Callers that read 0 as "disabled", or clamp
it upward themselves, keep their exact current meaning — whether 0 should stay meaningful
is part of Q3 and is not decided here. Only input that was never valid behaves differently.

Whole-class review. Three parsers read these overrides. readNonNegativeNumberEnv
already falls back on a negative. The inline parser in connect.ts already rejects <= 0
and warns. envInt was the only one that did not, so the class is this single function and
every call site inherits the fix; no call site needed its own edit. The local
Math.max(1, ...) in docker-gpu-supervisor-reconnect.ts is left in place — it is that
caller's own floor, not a workaround this change makes redundant.

Tests. A new focused suite covers negatives (-1, -30, -0.4, -1e3), the non-finite
inputs that already fell back (abc, NaN, Infinity, -Infinity), unset and empty, the
rounding contract (0.40, 2.63), the supplied-env-map path, and a regression
lock that an explicit 0 still returns 0. The debounce test that encoded the old negative
behaviour is updated to assert the fallback, next to a comment explaining why it now matches
the "abc" case directly above it.

Verification

npx vitest run src/lib/onboard/ — 3654 passed. Two pre-existing failures in
preflight.test.ts (checkPortAvailable) are unrelated to this change and reproduce on a
clean main checkout: they need a free local port that this machine does not have.

Changes

  • src/lib/onboard/env.ts: fall back on a negative override instead of clamping it to zero
  • src/lib/onboard/env-int.test.ts: new focused suite for the override contract
  • src/lib/onboard/sandbox-readiness-tracing.test.ts: assert the fallback for a negative debounce override
  • docs/inference/configure-inference-timeouts.mdx: state the rounding and invalid-value contract for these settings

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 (touched files at minimum)
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make 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)

AI Disclosure

  • AI-assisted — tool: Claude Code

Signed-off-by: Yanyun Liao yanyunl@nvidia.com

Summary by CodeRabbit

  • Bug Fixes
    • Invalid negative or non-finite timeout/debounce settings now fall back to the documented defaults instead of clamping to zero or triggering immediate behavior.
    • Negative values are treated as invalid (including in debounce poll handling), while valid non-negative inputs are still rounded.
  • Documentation
    • Clarified that timeout values must be whole-number seconds; fractional values are rounded, and unsupported/negative values use the default.
  • Tests
    • Strengthened onboarding override tests to ensure explicit env-map values are honored and added checks for empty, fractional, negative, and non-finite cases.

`envInt` backs the poll counts, poll intervals and readiness budgets used
across onboarding and gateway recovery. It clamped a negative override to
0 while falling back to the caller's default for every other unusable
value, so the same function answered two kinds of invalid input in
opposite ways -- and picked the most damaging reading for one of them: 0
empties a poll loop, zeroes a readiness budget, and turns
NEMOCLAW_GATEWAY_START_TIMEOUT into a timeout that expires before its
first attempt.

Treat a negative the way the sibling readNonNegativeNumberEnv already
does and fall back. An explicit 0 is unchanged, so callers that read it
as "disabled" or clamp it upward themselves keep their meaning; only
input that was never valid changes. One caller already compensated with
a local Math.max(1, ...); the rest inherited the hazard.

Refs #7881

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>
@yanyunl1991 yanyunl1991 added the bug-fix PR fixes a bug or regression label Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c22123bc-25f7-467d-bcb8-701a5686d4e1

📥 Commits

Reviewing files that changed from the base of the PR and between 3cca519 and b705588.

📒 Files selected for processing (1)
  • src/lib/onboard/env-int.test.ts

📝 Walkthrough

Walkthrough

envInt now treats negative and non-finite overrides as invalid fallbacks, preserves zero, and rounds valid non-negative values. Tests and inference timeout documentation reflect these rules.

Changes

Timeout environment validation

Layer / File(s) Summary
Update envInt fallback semantics
src/lib/onboard/env.ts
Negative and non-finite values return the fallback; valid non-negative values are rounded.
Validate timeout override behavior
src/lib/onboard/env-int.test.ts, src/lib/onboard/sandbox-readiness-tracing.test.ts, docs/inference/configure-inference-timeouts.mdx
Tests cover supplied-map precedence, fractional rounding, and negative-value fallback; documentation describes timeout rounding and invalid-input handling.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: brandonpelfrey, cv, ericksoa

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main behavior change: negative onboarding timeout and poll overrides now fall back instead of clamping.
Linked Issues check ✅ Passed The envInt fix and updated tests match #7881: negative values fall back to the default while explicit 0 remains valid.
Out of Scope Changes check ✅ Passed The added docs and test coverage stay aligned with the envInt behavior change and its related onboarding timeout usage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 fix/envint-rejects-negative-7881

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

@github-code-quality

github-code-quality Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit e50accf in the fix/envint-rejects-n... branch remains at 96%, unchanged from commit e824843 in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit e50accf in the fix/envint-rejects-n... branch remains at 81%, unchanged from commit e824843 in the main branch.

Show a code coverage summary of the most impacted files.
File main e824843 fix/envint-rejects-n... e50accf +/-
src/lib/domain/.../connect-env.ts 97% 89% -8%
src/lib/onboard/env.ts 100% 100% 0%
src/lib/sandbox...rce-identity.ts 88% 88% 0%

Updated August 01, 2026 10:56 UTC

@github-actions

Copy link
Copy Markdown
Contributor

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

🤖 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/env-int.test.ts`:
- Around line 60-62: Strengthen the test around envInt by supplying a
non-default value for NEMOCLAW_TEST_KNOB in the custom environment map and
asserting that value is returned. Ensure the assertion distinguishes the
supplied map from process.env, using a conflicting process value only if it can
be set and restored deterministically.
🪄 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: f90a0855-09f8-4de0-89e7-3b7e8c5c34b2

📥 Commits

Reviewing files that changed from the base of the PR and between 4dcb89e and 3cca519.

📒 Files selected for processing (4)
  • docs/inference/configure-inference-timeouts.mdx
  • src/lib/onboard/env-int.test.ts
  • src/lib/onboard/env.ts
  • src/lib/onboard/sandbox-readiness-tracing.test.ts

Comment thread src/lib/onboard/env-int.test.ts
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Informational

Advisor assessment: Informational / low confidence
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions
Status: Same-session synthesis validation failed; the advisor result is incomplete.

Model lanes

  • GPT-5.6 Terra (primary): Completed · low confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · low confidence · 0 blockers · 0 warnings · 0 suggestions
  • Model comparison: normalized findings match; normalized E2E selections differ; severity counts match.
1 additional E2E selection from the second opinion

Advisory only. The primary lane did not select these E2E jobs or targets.

  • gateway-guard-recovery: The completed second-opinion lane identified E2E coverage that the primary lane omitted.

Second-opinion E2E selections are advisory. They do not change the primary assessment or E2E / PR Gate.

E2E guidance

Advisory only. E2E / PR Gate selects and runs jobs independently.

Recommended E2E: onboard-repair, onboard-resume, cloud-onboard

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

…onment

The custom-environment case asserted the fallback while passing an empty map.
That passes even if `envInt` ignores the supplied map and reads
`process.env`, because the key is absent there too, so the assertion proved
nothing about the precedence its title claims.

Stub a conflicting process value, then assert both directions: a supplied value
is read, and an absent key falls back rather than picking up the process value.
Teardown runs in a `finally` so a failing expectation cannot leak the stub.

Verified by mutation: rewriting `envInt` to read `process.env[name]` now fails
this test, which it did not before.

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>

@cjagwani cjagwani left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved exact head b7055880323b0a352537bd0f567d0a74ed4e3ee2.

envInt now treats negative finite values consistently with other invalid inputs while preserving explicit zero and existing rounding semantics. The focused table tests cover negative, non-finite, empty, zero, fractional, and supplied-environment precedence cases; the debounce test verifies the affected caller contract. All 53 exact-head checks are green, DCO and commit verification pass, and no unresolved major/critical findings remain. Stale-base-only failure is waived because GitHub reports MERGEABLE/conflict-free.

@prekshivyas
prekshivyas merged commit 43eb929 into main Aug 1, 2026
67 of 70 checks passed
@prekshivyas
prekshivyas deleted the fix/envint-rejects-negative-7881 branch August 1, 2026 11:09
senthilr-nv added a commit that referenced this pull request Aug 4, 2026
<!-- markdownlint-disable MD041 -->
## Summary

Adds the canonical dated `v0.0.101` changelog entry that was missing
when the release tag was cut. This post-release recovery records the
shipped behavior on current `main` without changing or replacing the
existing tag.

## Changes

- Add `docs/changelog/2026-08-03.mdx` with the exact `## v0.0.101`
heading, release summary, detailed behavior changes, support boundaries,
and links to durable documentation.
- [#7317](#7317) ->
`docs/changelog/2026-08-03.mdx`: Records experimental OpenClaw Google
Chat support and its restricted credential and webhook boundary.
- [#7715](#7715) ->
`docs/changelog/2026-08-03.mdx`: Records strict onboarding recovery
state and authoritative resume identity.
- [#7749](#7749) ->
`docs/changelog/2026-08-03.mdx`: Records the provider-neutral policy
seam and unchanged runtime support boundary.
- [#7817](#7817) ->
`docs/changelog/2026-08-03.mdx`: Records preserved Hermes home-channel
assignments across rebuilds.
- [#7820](#7820) ->
`docs/changelog/2026-08-03.mdx`: Records the SSH-session status field
correction.
- [#7847](#7847) ->
`docs/changelog/2026-08-03.mdx`: Records fail-closed credential
filtering for migration and rebuild backups.
- [#7870](#7870) ->
`docs/changelog/2026-08-03.mdx`: Records sandbox-qualified in-sandbox
host command hints.
- [#7875](#7875) ->
`docs/changelog/2026-08-03.mdx`: Records Microsoft Teams stop and start
E2E coverage.
- [#7885](#7885) ->
`docs/changelog/2026-08-03.mdx`: Records Hermes managed gateway
detection in status.
- [#7889](#7889) ->
`docs/changelog/2026-08-03.mdx`: Records policy-authenticated HTTPS Pin
Runtime route revocation.
- [#7891](#7891) ->
`docs/changelog/2026-08-03.mdx`: Records default fallback for negative
timeout and polling overrides.
- [#7993](#7993) ->
`docs/changelog/2026-08-03.mdx`: Records correct sibling detection
during uninstall.
- [#7995](#7995) ->
`docs/changelog/2026-08-03.mdx`: Records absent configuration-hash
handling before shields lock.
- [#8001](#8001) ->
`docs/changelog/2026-08-03.mdx`: Records the dormant atomic managed
workload replacement foundation.
- [#8029](#8029) ->
`docs/changelog/2026-08-03.mdx`: Records repository terminology review
in PR Review Advisor.
- [#8031](#8031) ->
`docs/changelog/2026-08-03.mdx`: Records provider-neutral managed
snapshot authority.
- [#8032](#8032) ->
`docs/changelog/2026-08-03.mdx`: Records immutable managed clone handoff
contracts.
- [#8034](#8034) ->
`docs/changelog/2026-08-03.mdx`: Records the dormant provider-owned
clone transaction surface.
- [#8035](#8035) ->
`docs/changelog/2026-08-03.mdx`: Records the dormant Hermes managed
clone broker boundary.
- [#8036](#8036) ->
`docs/changelog/2026-08-03.mdx`: Records the dormant transactional
managed bootstrap boundary.
- [#8037](#8037) ->
`docs/changelog/2026-08-03.mdx`: Records dormant Docker bootstrap
primitives and the unchanged provider support boundary.
- [#8070](#8070) ->
`docs/changelog/2026-08-03.mdx`: Records consolidated sandbox
resource-limit E2E coverage.
- [#8071](#8071) ->
`docs/changelog/2026-08-03.mdx`: Records escaped and bounded CLI
validation diagnostics.
- [#8081](#8081) ->
`docs/changelog/2026-08-03.mdx`: Records bounded linear snapshot Base64
validation.
- [#8085](#8085) ->
`docs/changelog/2026-08-03.mdx`: Records commit-bound workflow approval
for eligible same-repository maintainers.
- [#8088](#8088) ->
`docs/changelog/2026-08-03.mdx`: Records Hermes managed-policy E2E
selection.
- [#8090](#8090) ->
`docs/changelog/2026-08-03.mdx`: Records pinned CI search-tool
provisioning.
- [#8106](#8106) ->
`docs/changelog/2026-08-03.mdx`: Records fallback from failed managed
OpenShell gateway startup.
- [#8107](#8107) ->
`docs/changelog/2026-08-03.mdx`: Records Hermes adapter lifecycle E2E
selection.
- [#8128](#8128) ->
`docs/changelog/2026-08-03.mdx`: Records the dormant transactional
Docker bootstrap adapter and rollback authority.
- [#8140](#8140) ->
`docs/changelog/2026-08-03.mdx`: Records Slack conflict scope across
independent OpenShell gateways.
- [#8147](#8147) ->
`docs/changelog/2026-08-03.mdx`: Records completion of durable v0.0.100
documentation audit follow-ups.

## Type of Change

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

## Quality Gates

- [ ] Tests added or updated for changed behavior
- [ ] Existing tests cover changed behavior — justification:
- [x] Tests not applicable — justification: This documentation-only
recovery does not change executable behavior.
- [x] Docs updated for user-facing behavior changes
- [ ] Docs not applicable — justification:
- [ ] Sensitive paths changed (security, policy, credentials, preflight,
onboarding, inference, runner, sandbox, or messaging)
- [ ] Sensitive-path review completed or maintainer-approved waiver
recorded — reviewer/approval link/justification:
- [ ] Non-success, skipped, or missing CI check accepted by maintainer —
check name, approval link, and follow-up issue:

## Documentation Writer Review

- [x] Documentation writer subagent reviewed the completed changes
- Result: `docs-updated`
- Evidence: Independently reviewed `docs/changelog/2026-08-03.mdx` at
commit `0bebe1f568e3dc85cf410aac1dfb8f8830070b85`. Its blob is
`82887920f9720eafd75db6b2271c35f7477edb9b`. The entry follows the
writing guide, controlled terminology, changelog structure, MDX SPDX
format, literal CLI-name rule, and root-absolute route requirements. It
accurately records the `v0.0.100...v0.0.101` release range, Announcement
#8162, accepted scope boundaries, and shipped security behavior. There
are no code samples. Focused changelog tests and the documentation build
pass for this commit.
- Agent: Codex Desktop independent documentation writer
<!-- docs-review-head-sha: 0bebe1f -->
<!-- docs-review-agents-blob-sha:
3dd7c24 -->

## Security Review

- Result: `PASS`
- Reviewed commit: `0bebe1f568e3dc85cf410aac1dfb8f8830070b85`
- Base commit: `643a4ab8b5f583d8555192a37927268b26022c51`
- Findings: None.
- Secrets and credentials: `PASS`. No credential values or secret files
are present.
- Input validation and data sanitization: `PASS`. No executable input
path changes.
- Authentication and authorization: `PASS`. No identity or permission
logic changes.
- Dependencies and third-party libraries: `PASS`. No dependency changes.
- Error handling and logging: `PASS`. No runtime path changes;
diagnostic-security claims are precise.
- Cryptography and data protection: `PASS`. No implementation changes.
- Configuration and security controls: `PASS`. No configuration,
container, port, or HTTP changes.
- Security testing: `PASS`. No coverage is removed; the entry records
shipped test and security behavior.
- System security: `PASS`. No runtime control changes; dormant and
non-activation boundaries are explicit.
- Agent: Codex Desktop independent security reviewer

## Verification

- [ ] PR description includes a `Signed-off-by:` line and every commit
appears as `Verified` in GitHub — verification is pending after commit
`0bebe1f568e3dc85cf410aac1dfb8f8830070b85` is pushed.
- [ ] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or
`npm run validate:pr` passed after refreshing `origin/main` when hooks
were skipped or unavailable — commit hooks passed; pre-push is pending.
- [x] Targeted behavior tests pass for the current change set, or tests
are marked not applicable above — tests are not applicable to this
documentation-only recovery.
- [x] Applicable broad gate passed — not applicable to this
documentation-only recovery.
- [x] Quality Gates section completed with required justifications or
waivers
- [x] No secrets, API keys, credentials, or private keys are added by
this diff.
- [ ] `npm run docs` builds without warnings (doc changes only) — GitHub
documentation checks are pending.
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only) — independent documentation review passed.
- [x] New doc pages include SPDX header and frontmatter (new pages only)
— the native changelog entry uses the required parser-safe MDX SPDX
comment and intentionally has no frontmatter.

GitHub CI is authoritative.
Focused changelog tests and `npm run docs` passed after the merge
refresh.

---
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added experimental Google Chat support.
  * Improved runtime and session status visibility.
  * Added onboarding recovery and persistence safeguards.
  * Added snapshot validation and dormant managed-workload support.

* **Bug Fixes**
* Improved backup sanitization, route handling, and gateway reliability.

* **Documentation**
  * Added the v0.0.101 changelog and related updates.

* **Tests**
  * Expanded end-to-end coverage and strengthened trusted CI validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Co-authored-by: Carlos Villela <cvillela@nvidia.com>
Co-authored-by: Senthil Ravichandran <senthilr@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-fix PR fixes a bug or regression

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(onboard): a negative timeout override is clamped to zero instead of falling back

4 participants