Files
openclaw/scripts
Sebastien Tardif 1fa1507fc4 fix(install): harden stdin consumers to prevent pipe corruption in curl | bash (#87799)
* fix(install): harden stdin consumers to prevent pipe corruption in curl | bash

Redirect stdin from /dev/null for non-interactive subprocesses (npm install,
openclaw daemon restart, openclaw plugins update, openclaw dashboard) and
from /dev/tty for interactive ones (openclaw onboard in bootstrap). Also
protect the fallback paths in run_with_spinner and run_quiet_step.

This prevents subprocesses from consuming the script stream when the
installer is piped via curl | bash, which causes truncated function names
and hangs (reported in #73814).

Unlike the global pipe guard in #82918 (closed as too broad), this approach
has no detection heuristics and no re-execution. Each subprocess simply gets
the correct stdin for its purpose.

Fixes #73814

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* chore: retrigger proof evaluation

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* fix: redirect stdin in run_with_spinner raw-mode fallback

The success-path raw-mode fallback in run_with_spinner invoked the
command with inherited stdin. In a curl | bash scenario, this allowed
the child process to consume bytes from the script stream, causing
truncation. Add < /dev/null to match the other two fallback paths.

* retrigger proof check

* fix(test): update gum fallback assertion for stdin redirect

* fix: redirect gum-wrapped stdin and preserve TTY for interactive commands

Address ClawSweeper P1 and P2 findings:

- P1: Redirect stdin from /dev/null on the normal gum spin path so child
  commands cannot consume the piped script stream (gum v0.17.0 passes
  os.Stdin to wrapped commands).

- P2: Use is_non_interactive_shell to conditionally redirect stdin in
  fallback paths. When running interactively (bash install.sh), commands
  that need user input (e.g. Homebrew prompts) keep terminal stdin.
  When piped (curl | bash), stdin is redirected from /dev/null.

- P2: Revert labeler.yml changes to keep the PR focused on installer
  stdin safety. Size-label best-effort handling belongs in a separate PR.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* chore: retrigger proof evaluation

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* retrigger proof check

* fix: base stdin isolation on stdin TTY check, not stdout

Replace is_non_interactive_shell with needs_stdin_isolation for stdin
redirection decisions. The new function checks stdin directly (! -t 0)
and NO_PROMPT, without checking stdout (-t 1). This ensures that
stdout redirection (e.g. install.sh > log.txt) does not suppress
interactive prompts when stdin is a terminal.

* test(install): add stdin isolation and NO_PROMPT coverage

Three focused tests for the needs_stdin_isolation function:

1. Verify needs_stdin_isolation returns true when stdin is piped
   (the core curl|bash scenario).
2. Verify needs_stdin_isolation returns true when NO_PROMPT=1 is set
   (explicit non-interactive override).
3. Verify run_quiet_step redirects subprocess stdin to /dev/null
   when running in a piped context, preventing script consumption.

* test(install): strengthen stdin isolation test with sentinel data

Replace the weak TTY check (which passes even without the fix since
the test pipe is also non-TTY) with a sentinel-based test: pipe
SENTINEL_DATA on stdin and verify the child process reads nothing,
proving run_quiet_step actually redirects stdin to /dev/null.

* test(install): add counterproof and cat-based stdin isolation tests

Add two new tests to prove the stdin isolation fix is necessary and
works correctly:

- counterproof: demonstrates that pipe data DOES leak to the child
  when stdin is not redirected through run_quiet_step, proving the
  /dev/null redirect is the isolation barrier
- cat-based test: uses cat (reads all of stdin) instead of read -t 1
  (timeout-based) for a deterministic assertion that run_quiet_step
  produces empty stdin

* fix: gate gum spin stdin redirect on needs_stdin_isolation

The gum spin command unconditionally redirected stdin from /dev/null,
which also killed interactive prompts for direct installs since gum
v0.17 passes os.Stdin to the wrapped command. Now only redirect
stdin when needs_stdin_isolation returns true (piped install context).

Adds focused tests verifying both paths: piped installs get /dev/null
redirect, direct interactive installs preserve terminal stdin.

* test: assert gum stdin is not /dev/null for direct interactive installs

The direct gum runtime test now reads the child command's stdin-source
log file and asserts stdin was NOT /dev/null, using device:inode
comparison (stat -f on macOS, stat -c on Linux) for reliable detection
across both platforms.

* retrigger proof check

* retrigger proof check with real installer evidence

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* fix(install): preserve interactive post-install stdin

* fix(install): route piped prompts through controlling tty

* fix(install): keep quiet piped steps noninteractive

* fix(install): avoid hidden prompts with redirected output

* fix(install): preserve visible prompt output state

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
2026-07-11 14:00:53 +08:00
..
2026-07-10 17:47:32 -07:00
2026-07-01 01:51:50 -07:00
2026-06-04 23:52:06 -04:00

summary, read_when, title
summary read_when title
Repository script entry points and compatibility notes
Looking for an existing script before adding a new one
Running repository checks, tests, docs, Docker, release, or GitHub helper scripts
Updating package scripts or CI workflow script references
Scripts Directory

Scripts Directory

The scripts/ directory contains repository tooling used by local development, CI, docs publishing, releases, Docker proof, and maintainer operations. Prefer the package-script entry points in package.json when one exists, then read the underlying script before running it directly.

Compatibility

Many scripts are stable paths referenced by package.json, GitHub Actions, docs, and maintainer runbooks. Do not move, rename, or regroup scripts only to improve taxonomy. A directory migration needs an explicit maintainer-approved compatibility plan for package scripts, workflows, docs snippets, and any raw script paths users may have copied.

This index is a discovery aid for the current flat layout. It does not define a new directory taxonomy.

Common Entry Points

Area Prefer Notes
Build pnpm build Runs scripts/build-all.mjs; use specific build scripts only when debugging a build stage.
Changed checks pnpm changed:lanes --json, pnpm check:changed Lane classification lives in scripts/changed-lanes.mjs; changed-file checks live in scripts/check-changed.mjs.
Docs pnpm docs:list, pnpm docs:check-mdx, pnpm docs:check-links Backed by scripts/docs-list.js, scripts/check-docs-mdx.mjs, and scripts/docs-link-audit.mjs.
Formatting docs pnpm format:docs:check Uses scripts/format-docs.mjs; use write mode only when intentionally formatting docs.
Lint pnpm lint, pnpm lint:core, pnpm lint:all Wrapper scripts keep oxlint behavior aligned with repo config.
Targeted tests pnpm test <path-or-filter> or node scripts/run-vitest.mjs <path-or-filter> Avoid bare vitest; it can start watch mode.
Changed tests pnpm test:changed Uses the repo's changed-test resolver instead of a broad Vitest run.
Docker proof pnpm test:docker:all, pnpm test:docker:rerun, pnpm test:docker:timings Use the planner/rerun helpers before launching broad Docker work.
Live proof pnpm test:live Live checks require the matching environment and credentials.
Release checks pnpm release:check, pnpm release:beta, pnpm release:candidate Release scripts are maintainer workflows; read release docs before use.
GitHub reads scripts/gh-read Uses a GitHub App read token when configured, leaving normal gh login for writes.
Commits scripts/committer "<message>" <files...> Preferred scoped commit helper for OpenClaw changes.
Remote proof node scripts/crabbox-wrapper.mjs ... Agent default for tests and heavy work; pre-warm by source trust, sync each run, reuse the lease.

Script Families

  • check-*.mjs / check-*.ts: guardrails for architecture, docs, package contents, boundaries, workflows, and generated artifacts.
  • run-*.mjs: wrappers around repo runtimes or tools, such as Node, Vitest, oxlint, tsgo, and environment setup.
  • test-*.mjs / test-*.sh / test-*.ts: test planners, Docker lanes, live checks, and focused validation helpers.
  • docs-* and check-docs-*: docs listing, link auditing, MDX checks, spellcheck, sync, and i18n glossary checks.
  • release-*, openclaw-npm-*, and plugin-*-release-*: release preparation, package verification, and publishing helpers.
  • docker-*, test-docker-*, and test-live-*-docker.sh: Docker E2E planning, rerun, timing, and live/package lane helpers.
  • gh-read*, label-*, sync-labels.ts, and PR helpers: GitHub read, labeling, and maintainer workflow support.
  • generate-*, write-*, copy-*, and sync-*: generated docs, metadata, package surfaces, and build artifact support.
  • lib/: shared helpers imported by script entry points.

Maintenance Rules

  • Read scripts/AGENTS.md before changing scripts.
  • Keep package scripts, generators, generated-artifact checks, docs references, and workflow references aligned when touching a script path.
  • Prefer existing wrappers instead of introducing a raw tool invocation.
  • Add or update focused tests under test/scripts/ when changing script behavior.

See also Scripts for public-facing script guidance.