diff --git a/.github/workflows/website-installer-sync.yml b/.github/workflows/website-installer-sync.yml index 51c5d1c7b351..b752017d795e 100644 --- a/.github/workflows/website-installer-sync.yml +++ b/.github/workflows/website-installer-sync.yml @@ -76,11 +76,9 @@ jobs: - name: install.sh in Docker run: | docker run --rm \ - -e OPENCLAW_NO_ONBOARD=1 \ - -e OPENCLAW_NO_PROMPT=1 \ -v "$PWD/scripts/install.sh:/tmp/install.sh:ro" \ node:24-bookworm-slim \ - bash -lc 'bash /tmp/install.sh --no-prompt --no-onboard --version latest && openclaw --version' + bash -lc 'bash /tmp/install.sh --version latest && openclaw --version' - name: install-cli.sh in Docker run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 036f8ab4c538..e0959c22f6ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Docs: https://docs.openclaw.ai - Providers/Ollama: treat Docker/OrbStack host aliases as local Ollama endpoints so `ollama-local` marker auth works when OpenClaw runs inside a VM/container and Ollama runs on the host. Fixes #84875. - QA-Lab: keep explicitly searchable/deferred OpenClaw dynamic tool rows report-only by default so tool-coverage gates do not treat mock discovery gaps as hard product failures. (#80319) Thanks @100yenadmin. - Agents/config: keep non-Google provider model refs from being rewritten by Google Gemini preview-id normalization. (#84762) Thanks @zhangguiping-xydt. +- Installer: require a real controlling terminal before launching onboarding so headless `curl | bash` installs finish cleanly after installing the CLI. - Agents: cap heartbeat model bleed context hints by the stored session window when runtime model metadata is unavailable, so overflow recovery advice does not suggest a larger window than the active session actually has. - Control UI/Web Push: use `https://openclaw.ai` as the generated default VAPID subject instead of the old localhost mailbox so iOS PWA push setup uses an Apple-acceptable subject when `OPENCLAW_VAPID_SUBJECT` is unset. Fixes #83134. (#83317) Thanks @IWhatsskill. - Agents/Pi: keep embedded session transcript writes from tripping false takeover detection after packaged npm onboarding agent turns. diff --git a/scripts/install.sh b/scripts/install.sh index 8ca52e1f9cf5..de3603cdd58d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -90,6 +90,16 @@ is_non_interactive_shell() { return 1 } +has_controlling_tty() { + if [[ ! -r /dev/tty || ! -w /dev/tty ]]; then + return 1 + fi + if ! { : /dev/null; then + return 1 + fi + return 0 +} + gum_is_tty() { if [[ -n "${NO_COLOR:-}" ]]; then return 1 @@ -100,7 +110,7 @@ gum_is_tty() { if [[ -t 2 || -t 1 ]]; then return 0 fi - if [[ -r /dev/tty && -w /dev/tty ]]; then + if has_controlling_tty; then return 0 fi return 1 @@ -1152,7 +1162,7 @@ is_promptable() { if [[ "$NO_PROMPT" == "1" ]]; then return 1 fi - if [[ -r /dev/tty && -w /dev/tty ]]; then + if has_controlling_tty; then return 0 fi return 1 @@ -2577,7 +2587,7 @@ run_bootstrap_onboarding_if_needed() { return fi - if [[ ! -r /dev/tty || ! -w /dev/tty ]]; then + if ! is_promptable; then local user_claw user_claw="$(openclaw_command_for_user "${OPENCLAW_BIN:-}")" ui_info "BOOTSTRAP.md found but no TTY; run ${user_claw} onboard to finish setup" @@ -2962,7 +2972,7 @@ main() { ui_kv "Switch to npm" "curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method npm" elif [[ "$is_upgrade" == "true" ]]; then ui_info "Upgrade complete" - if [[ -r /dev/tty && -w /dev/tty ]]; then + if has_controlling_tty || [[ "$NO_ONBOARD" == "1" || "$NO_PROMPT" == "1" ]]; then local claw="${OPENCLAW_BIN:-}" if [[ -z "$claw" ]]; then claw="$(resolve_openclaw_bin || true)" @@ -3010,7 +3020,7 @@ main() { fi ui_info "Starting setup" echo "" - if [[ -r /dev/tty && -w /dev/tty ]]; then + if is_promptable; then local claw="${OPENCLAW_BIN:-}" if [[ -z "$claw" ]]; then claw="$(resolve_openclaw_bin || true)" diff --git a/test/scripts/install-sh.test.ts b/test/scripts/install-sh.test.ts index 74d0cf55be6a..ddfb97a443fc 100644 --- a/test/scripts/install-sh.test.ts +++ b/test/scripts/install-sh.test.ts @@ -431,6 +431,19 @@ describe("install.sh", () => { expect(script).toContain('corepack prepare "pnpm@${version}" --activate'); expect(script).toContain('activate_repo_pnpm_version "$repo_dir"'); }); + + it("does not treat /dev/tty permissions as a controlling terminal", () => { + const result = runInstallShell(` + set -euo pipefail + source "${SCRIPT_PATH}" + if has_controlling_tty; then echo "has_tty=1"; else echo "has_tty=0"; fi + if is_promptable; then echo "promptable=1"; else echo "promptable=0"; fi + `); + + expect(result.status).toBe(0); + expect(result.stdout).toContain("has_tty=0"); + expect(result.stdout).toContain("promptable=0"); + }); }); describe("install.sh macOS Homebrew Node behavior", () => { diff --git a/test/scripts/website-installer-sync-workflow.test.ts b/test/scripts/website-installer-sync-workflow.test.ts index 8cad5e158e51..0b91cc9ec814 100644 --- a/test/scripts/website-installer-sync-workflow.test.ts +++ b/test/scripts/website-installer-sync-workflow.test.ts @@ -23,7 +23,8 @@ describe("website installer sync workflow", () => { it("verifies installers on Linux Docker plus native macOS and Windows runners", () => { expect(workflow).toContain("linux-docker:"); expect(workflow).toContain("docker run --rm"); - expect(workflow).toContain("bash /tmp/install.sh --no-prompt --no-onboard"); + expect(workflow).toContain("bash /tmp/install.sh --version latest && openclaw --version"); + expect(workflow).not.toContain("bash /tmp/install.sh --no-prompt --no-onboard"); expect(workflow).toContain("bash /tmp/install-cli.sh --prefix /tmp/openclaw"); expect(workflow).toContain("macos-installer:"); expect(workflow).toContain("runs-on: macos-latest");