* fix(watch): defer restart when dist/ is mid-rebuild (#99603)
Defer gateway:watch child restart when run-node's build/runtime-postbuild
readiness contract reports a hard dist/ failure. Only defers on conditions
where the child physically cannot start, preserving normal restart behavior
for soft staleness that run-node can rebuild on start.
Hard failures (defer restart):
- missing_dist_entry (mid-rebuild)
- missing_bundled_plugin_dist_entry
- missing_private_qa_dist
- missing_runtime_postbuild_output
Soft staleness (allow restart, run-node rebuilds):
- missing_build_stamp (w/ direct entry.js check for masked case)
- git_head_changed, dirty_watched_tree
- config_newer, build_stamp_missing_head, source_mtime_newer
isBuildReadyForRestart delegates to resolveBuildRequirement and
resolveRuntimePostBuildRequirement from run-node.mjs, using the
watcher process environment for consistency with the child runner.
200ms polling with 5-min timeout. Child-exit bounded recovery.
Clears stale restartRequested on timeout/poll errors.
Ref: #99603
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(watch): deduplicate deferral loops, add single-flight guard, remove lint suppression
- Extract deferUntilBuildReady(onReady, onTimeout) shared helper,
replacing the near-identical pollBuildReady and pollAfterChildExit.
- Add single-flight guard (deferredRestartActive) so concurrent file
change events during deferral coalesce into one poll loop instead of
spawning a git-subprocess storm.
- Replace inline .catch arrow callback with method-style call, removing
the oxlint-disable-next-line suppression and its allowlist entry.
- Add tests: coalescing multiple changes, requestRestart recovery.
Ref: #99603
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: keep gateway watch alive during concurrent builds
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* test: fix changed-run routing, dedupe gateway package lanes, repair force-rerun triggers
- route extensions/active-memory changed runs to their dedicated lane; add a
generic guard that every catch-all-excluded extension root resolves to a
dedicated config so future split lanes cannot silently skip changed tests
- exclude packages/gateway-client and packages/gateway-protocol from the unit
lane; explicit targets now route to the gateway-client lane (was double-run)
- generate forceRerunTriggers from the test/vitest directory and resolve all
entries absolute: Vitest matches triggers against absolute changed-file
paths, so the old hand-maintained relative list never matched at all
- replace deprecated envFile:false with envDir:false (Vitest 4.1.9 warning)
- give scripts/test-live.mjs stall detection teeth: kill the process group and
exit non-zero when OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS elapses quietly
- consolidate the live-docker shell target list into changed-lanes.mjs so the
lane regex and check targets cannot drift; cover subagent-announce edits
- delete orphans: vitest.extension-clickclack.config.ts,
vitest.full-core-unit.config.ts, scripts/test-docker-all.sh,
scripts/test-live-acp-spawn-defaults-docker.sh
Closes#104231
* test: keep envDir literal so sharedVitestConfig satisfies Vite UserConfig
Vite 8 types envDir as string | false; the bare object literal widened false to
boolean and broke check:test-types at the defineConfig call sites.
* feat: sidebar update card (web + macOS) with app-first mac update flow and Sparkle beta track
Squashed from claude/update-notification-display-c6cfb9 after semantic merge
with #104178 (channel-aware CLI installs). See PR #104171 body for details.
* chore(i18n): resync generated inventories after rebase
* chore(i18n): resync locale metadata after rebase
Adds a git-backed session diff panel to the Control UI, complementing the existing PR status chips.
New sessions.diff gateway method (operator.read): resolves the session checkout and returns structured per-file diffs (status, renames, +/- counts, capped unified patch) of branch + uncommitted + untracked work against the default-branch merge base. Hardened against git textconv execution and hardlinked out-of-tree content leaks; handles repos before their first commit via the empty-tree base.
Control UI renders a "Changes" panel in the chat detail sidebar with collapsible per-file diffs, hunk-gap markers, stat chips, and untracked/binary badges, gated on gateway method advertisement. Schemas additive (Swift models regenerated), 20 locales translated.
Closes#104182
* 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>
* fix(logs): clean up gateway and channel startup/shutdown log output
Scope Discord slash-command deploy REST diagnostics to command routes so
concurrent startup traffic (voice-state probes, channel lookups) keeps its
owner's error handling; make per-request deploy error lines verbose-only and
drop JSON bodies that only repeat message+code. Log allowlist summaries one
line per call so unresolved lines keep their timestamp/subsystem prefix, and
skip identity lookups that resolved to themselves. Remove embedded subsystem
prefixes, demote routine signal/shutdown/force/postbuild/diag chatter to
debug or verbose, merge the duplicate Control UI build notices, drop doctor's
duplicate backup line, and name the config surface or platform limit in the
transcripts autoStart and command-limit warnings.
Fixes#104163
* fix(slack): keep bare-name allowlist resolutions in startup log summary
Only omit identity lookups where the input already is the resolved id;
name-based lookups that translated to an id stay logged even when the
display name matches the input.
* fix(telegram): keep isolated-ingress readiness marker on the runtime log
test/e2e/qa-lab telegram-bot-token-runtime waits for this line via the
injected RuntimeEnv.log; verbose-only logging would deterministically time
out that live proof. Comment the contract at the call site.