mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-04 07:34:25 +02:00
* fix(daemon): diagnose silent OpenClaw npm shim failures on Windows (MUL-5422)
#6061 reported every OpenClaw task failing in execenv prep on Windows with
a bare `exit status 1` and no stderr, leaving the user nothing to act on.
An npm-installed `openclaw.cmd` is a batch shim that re-execs OpenClaw's
`openclaw.mjs` entrypoint through `node`, resolved from PATH. The daemon pins
`openclaw` to an absolute path, so the shim always looks correct — but that
interpreter lookup is a second, invisible resolution step that can fail on its
own. The reporter had to run their own subprocess experiments to find it.
Enrich the error instead of guessing at a fix: when a `.cmd`/`.bat` shim exits
non-zero with no stderr, report whether the interpreter resolves. Both
directions are useful — missing names the likely cause with a next step,
present clears PATH of blame and points at the remaining hypotheses (PATH
drift between the runtime `--version` gate and task prep, or a broken install).
Deliberately NOT included: rebuilding or freezing a Windows PATH. The
version-probe gate (probeBuiltinRuntime skips a provider whose `--version`
fails) and the prep helper both inherit the same daemon environment, so a
daemon that could not resolve `node` would never have registered OpenClaw at
all. That contradiction is unresolved, and a boot-time PATH snapshot would also
fight the MUL-4486 self-heal design, which re-resolves per attempt on purpose.
This change collects the evidence needed to settle it.
- Error text only; no control flow change, and real stderr still wins.
- PATH summarised as an entry count, never dumped, so daemon logs and pasted
bug reports carry no environment detail.
- Tests: shim detection (case, spaces, Unicode), both diagnostic directions,
out-of-scope no-ops (timeout, missing binary, native exe), and end-to-end
through execOpenclawCLI. A windows-tagged file reproduces a real npm shim
with and without node on PATH, and pins TEMP/TMP as not load-bearing — the
originally reported root cause, since retracted upstream.
- New scoped step in the existing ci.yml windows-execenv job.
Co-authored-by: multica-agent <github@multica.ai>
* fix(daemon): address review on OpenClaw shim diagnostics (MUL-5422)
Four must-fix items from PR review, each verified against the real behaviour
rather than assumed.
1. Timeout was misdiagnosed as a missing interpreter. openclawCLITimeout kills
the child via CommandContext, and a killed process surfaces as
*exec.ExitError ("signal: killed") — the same type a genuine exit 1
produces. The errors.As gate accepted it and appended "install Node.js",
sending users to fix something that was never broken. execOpenclawCLI now
attributes ctx.Err() before consulting the diagnostic. Confirmed locally:
`signal: killed`, errors.As(*exec.ExitError)=true, ctx.Err() set. The old
test passed context.DeadlineExceeded directly and so never saw the real
shape; replaced with a genuine CommandContext timeout regression on both
Linux and Windows.
2. The interpreter lookup did not match npm's. npm's cmd-shim template emits
`IF EXIST "%dp0%\node.exe" (...) ELSE ( SET "_prog=node" )`, so a co-located
node.exe wins over PATH entirely. Checking only LookPath reported "node is
not resolvable" for installs that actually run fine — confidently wrong,
which is worse than silence. Now resolves co-located `node.exe`/`node`
first, then PATH, and reports which. Wording is also conditional now
("if <name> is an npm-generated shim"): a batch extension does not prove npm
authorship, since MULTICA_OPENCLAW_PATH can point at any batch file.
3. The message leaked local paths off-box. On prep failure this text is not
log-local — it travels reportTerminalTask → Client.FailTask and is persisted
server-side as the task error, so an absolute Windows shim path uploads the
account name and install layout. Now reports only the shim's base name,
whether the interpreter resolved and from where, and a PATH entry count.
Never an absolute path, never PATH contents.
4. Windows CI was green without exercising the new code. The job log showed
`cmd.exe stderr DID reach Go's pipe` with `'node' is not recognized`, so the
missing-node case takes the existing stderr branch and the diagnostic never
ran — masked by an "either branch passes" assertion. That disjunction is
gone: the missing-node test now asserts the observed stderr behaviour
(disproving #6061's premise), and a new test drives a genuinely silent shim
to prove the diagnostic branch itself works on Windows. Also added Windows
coverage for the co-located interpreter and the timeout case.
The windows-tagged shim is now npm's real generated template rather than a
hand-simplified `node ...` one-liner, so the co-located branch is reproduced
faithfully instead of hidden.
Co-authored-by: multica-agent <github@multica.ai>
* test(daemon): make the OpenClaw timeout regression PATH-independent
The new timeout test stripped PATH (so a stray interpreter lookup would report
"missing") while its hanging shim invoked `sleep` through a PATH lookup. macOS
`sh` quietly falls back to a default PATH so this passed locally; dash on Linux
does not, so CI failed with `exit status 127 (stderr: sleep: not found)` — the
shim died instantly instead of hanging, and the assertion never saw a timeout.
Resolve `sleep` before PATH is stripped and embed it by absolute path, so the
shim needs no PATH of its own. Verified the failure mode and the fix directly:
`env -i /bin/sh -c 'PATH=/nonexistent; sleep 0.05'` reproduces
"sleep: command not found", while the absolute path runs fine with the same
empty PATH.
Windows is skipped here and covered by TestWindowsOpenclawShimTimeoutIsNotMisdiagnosed,
which has a real cmd.exe host and a System32 PATH that can resolve its own helper.
Co-authored-by: multica-agent <github@multica.ai>
* fix(daemon): bound execOpenclawCLI so its 5s timeout is actually enforceable
The new timeout regression exposed a real bug in the code it was testing, not
just a flaky test: openclawCLITimeout could not bound the call at all.
CommandContext kills only the direct child, and cmd.Output() blocks in Wait()
until the stdout pipe closes. Any grandchild that inherited stdout keeps the
call parked for its own lifetime. Verified on linux/dash: a shim whose child
slept 5s ran the FULL 5.01s against a 150ms deadline. With a WaitDelay backstop
the same case returns in ~2.17s.
This is not a hypothetical shape — it is precisely an npm shim on Windows
(cmd.exe → node), so a wedged node could stall task prep far past the 5s cap
that comment claims. detectCLIVersion already carries this exact backstop for
the `--version` probe for the same reason; execOpenclawCLI now matches it.
Also corrected the test comment: an earlier revision claimed a trailing
`exit 0` was needed to force the grandchild. Docker showed otherwise — dash
hangs either way and macOS reproduces neither, which is why CI caught this and
local runs did not. The comment now records the measured behaviour.
Verified in a linux/dash container (the CI platform, not just macOS): the full
execenv package passes with -race, and the timeout case takes 2.17s.
Co-authored-by: multica-agent <github@multica.ai>
* fix(daemon): drop the WaitDelay change and wrap the context error (MUL-5422)
Round-2 review: take option 1 — keep this PR to diagnostics and split the
timeout/process-tree work out.
The reviewer is right that WaitDelay traded a hang for a process leak, and I
had the mechanism wrong. Measured on linux/dash by recording the grandchild PID
and reading /proc/<pid>/stat at the moment Output returns:
no WaitDelay: elapsed 6.01s (6s sleep, 150ms deadline), grandchild state Z
with WaitDelay: elapsed 2.17s (60s sleep, 150ms deadline), grandchild state S
So without WaitDelay the call is hostage to the descendant's lifetime but no
live process is left behind — it returned precisely because the descendant had
exited. With WaitDelay the call is bounded but a live descendant survives. My
earlier claim that the orphan pre-existed was an artifact of a sleep duration
that happened to equal the return time.
Go's WaitDelay contract covers killing the direct child and closing our pipe
ends; it does not reap orphans. Closing this properly needs process-tree
ownership (Unix process group, Windows Job Object) so the deadline can terminate
the whole tree — and on Unix nothing else will, since
preparationProcessController.finish() is a no-op there (isolation_unix.go).
That is its own change with its own risk surface, so it is tracked separately
and openclawCLITimeout now documents the gap with the measurements rather than
shipping half a fix.
Also fixes the round-2 nit: the context branch %w-wrapped the process error
while printing ctxErr with %v, so errors.Is(err, context.DeadlineExceeded) was
false despite the text containing it. The context error is now the wrapped
cause and the process error is attached for diagnosis:
openclaw config file: context deadline exceeded (process: signal: killed)
Tests: the timeout cases no longer depend on WaitDelay and no longer leave a
live process — short sleeps keep them about attribution, which is what they are
for. Added an explicit errors.Is assertion for both DeadlineExceeded and
Canceled. Verified in a linux/dash container (the CI platform): full execenv
package passes with -race and `ps` shows no leftover sleep processes.
Co-authored-by: multica-agent <github@multica.ai>
* docs(daemon): correct two stale comments on the OpenClaw CLI timeout (MUL-5422)
Both nits from the third review. Comment-only; no code change.
1. openclawCLITimeout's doc contradicted itself — it opened with "caps ...
without letting a hung CLI stall task dispatch indefinitely" and then
explained that the deadline cannot actually bound the call. Reworded to say
what it is (a 5s context deadline) and to point at the gap rather than assert
a guarantee it does not provide. Also names MUL-5467 instead of the vague
"tracked separately".
2. The two timeout tests claimed a long wait would "leave a live process
behind". That described the reverted WaitDelay behaviour, not the current
code. Without WaitDelay, cmd.Output() returns only once the descendant has
closed stdout — its exit is what produces the EOF — so a long wait makes the
test slow, it does not leak. Re-verified on linux/dash after the fix: the
case takes 1.01s for a 1s sleep and `ps` shows no leftover process, and the
earlier PID probe recorded the grandchild in state Z at the return point.
Rebased onto c25a82eee.
Co-authored-by: multica-agent <github@multica.ai>
* docs(daemon): tighten OpenClaw timeout wording and pipe-EOF claims (MUL-5422)
Round-4 review nits. Comment-only; verified no non-comment line changed.
1. OpenclawConfigPrep.Timeout still said it "caps each CLI invocation", which
contradicts the openclawCLITimeout doc corrected last round. It now says it
sets the context deadline and points at that note. Fixed the same word in the
struct's own doc comment, which had the identical claim.
2. The two timeout-test comments equated "descendant closes the pipe" with
"descendant has exited". That holds for these helpers but is not a general
property — a process can close its pipes and keep running — so the comments
now scope the claim to the helper and say so explicitly.
Also corrected "stdout" to the output pipes os/exec manages for both stdout
AND stderr. Verified rather than assumed: with cmd.Stderr set to an
in-memory writer (as execOpenclawCLI does), a grandchild holding EITHER
stream parks cmd.Output() for its full 3s lifetime, while one holding
neither returns in 0s. So Wait genuinely depends on both.
No rebase this round: the branch is 1 commit behind main, that commit does not
touch execenv, and GitHub already reports MERGEABLE — not worth another forced
CI rerun.
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
386 lines
16 KiB
YAML
386 lines
16 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# Decides whether the (heavy, ~6min) frontend job has anything to do.
|
|
# The frontend job validates the web/desktop apps, the shared packages,
|
|
# the install graph, and the selfhost / reserved-slugs scripts it runs;
|
|
# a pure backend-only or docs-only PR touches none of those and gains
|
|
# nothing from a full web build. This job emits a single `frontend`
|
|
# output consumed by the frontend job below.
|
|
changes:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
outputs:
|
|
frontend: ${{ steps.decide.outputs.frontend }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Filter paths
|
|
id: filter
|
|
uses: dorny/paths-filter@v3
|
|
with:
|
|
# apps/docs is excluded from the frontend turbo run, so a
|
|
# docs-only change does not need this job. apps/mobile has its
|
|
# own mobile-verify workflow. Everything else the frontend job
|
|
# touches is listed here; bias toward over-matching since a
|
|
# missed path silently skips validation.
|
|
filters: |
|
|
frontend:
|
|
- 'apps/web/**'
|
|
- 'apps/desktop/**'
|
|
- 'packages/**'
|
|
- 'package.json'
|
|
- '.npmrc'
|
|
- 'pnpm-lock.yaml'
|
|
- 'pnpm-workspace.yaml'
|
|
- 'turbo.json'
|
|
- '.github/workflows/ci.yml'
|
|
- 'scripts/generate-reserved-slugs.mjs'
|
|
- 'server/internal/handler/reserved_slugs.json'
|
|
- 'scripts/selfhost-config.test.sh'
|
|
- 'scripts/check.sh'
|
|
- 'scripts/dev.sh'
|
|
- 'scripts/local-env.sh'
|
|
- '.env.example'
|
|
- 'docker-compose.selfhost.yml'
|
|
|
|
- name: Decide
|
|
id: decide
|
|
# Always run the frontend job on push to main (full validation);
|
|
# on pull_request, run only when frontend-relevant paths changed.
|
|
# The frontend job itself always runs and reports success — its
|
|
# steps are gated on this output rather than the job being skipped
|
|
# — so the required "frontend" status check is satisfied with a
|
|
# genuine green instead of being left pending on filtered PRs.
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
FRONTEND_CHANGED: ${{ steps.filter.outputs.frontend }}
|
|
run: |
|
|
if [ "$EVENT_NAME" != "pull_request" ] || [ "$FRONTEND_CHANGED" = "true" ]; then
|
|
echo "frontend=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "frontend=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
# The frontend validation is split across two runners on purpose. Both
|
|
# `@multica/web:build` (a webpack production build) and `@multica/views:test`
|
|
# (259 jsdom files) are CPU-saturating, and a standard runner only has
|
|
# 4 vCPUs. Running them in one job made them starve each other: the views
|
|
# suite needs ~104s wall when it owns 4 cores but took ~500s sharing them,
|
|
# and the identical webpack compile went from ~26s to ~342s. Splitting buys
|
|
# a second 4-vCPU box rather than reducing the work; total runner-minutes go
|
|
# up slightly, wall-clock feedback time goes down.
|
|
#
|
|
# The split is weighted, not even: the test group is by far the heavier half
|
|
# (~575 CPU-seconds vs ~250 for build + typecheck + lint), so it gets a
|
|
# runner to itself and everything else shares the other one.
|
|
frontend-build:
|
|
needs: changes
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
# Pin turbo's filesystem cache somewhere actions/cache can address.
|
|
TURBO_CACHE_DIR: .turbo/cache
|
|
steps:
|
|
- name: Checkout
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup pnpm
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Setup Node.js
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
run: pnpm install
|
|
|
|
# `node-version: 22` above floats across patch releases, and turbo's
|
|
# global hash does not include the interpreter at all (`engines` is null
|
|
# in its dry-run cache inputs). Without the resolved version in the key,
|
|
# a runner silently moving to another 22.x would restore a cache built by
|
|
# the old interpreter and report green without executing anything.
|
|
- name: Resolve runtime for cache key
|
|
id: runtime
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
run: echo "node=$(node --version)" >> "$GITHUB_OUTPUT"
|
|
|
|
# Cache entries are immutable, so the key carries the commit SHA to make
|
|
# every run publish a fresh one and `restore-keys` falls back to the most
|
|
# recent prefix match. The two frontend jobs run disjoint task sets, so
|
|
# they get their own prefixes rather than racing to save one key.
|
|
# GitHub scopes caches by branch: a PR reads main's entries (so unchanged
|
|
# tasks hit on the first push) and writes its own (so re-pushes hit too).
|
|
- name: Restore turbo cache
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: .turbo/cache
|
|
key: turbo-build-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-${{ github.sha }}
|
|
restore-keys: |
|
|
turbo-build-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-
|
|
|
|
- name: Test self-host env derivation
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
run: bash scripts/selfhost-config.test.sh
|
|
|
|
- name: Verify reserved-slugs.ts is up to date
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
# Re-runs the generator and fails on any drift from the
|
|
# checked-in TypeScript output. The Go side embeds the JSON
|
|
# source directly, so a passing diff here proves both sides
|
|
# share one source of truth.
|
|
run: |
|
|
pnpm generate:reserved-slugs
|
|
git diff --exit-code -- packages/core/paths/reserved-slugs.ts
|
|
|
|
- name: Build, type check, and lint
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
# Mobile lives in a parallel mobile-verify workflow (path-filtered
|
|
# to apps/mobile/** + packages/core/**) so it doesn't add
|
|
# ~50s of expo-lint + tsc to every web/desktop PR. Keep this
|
|
# filter in sync with the root package.json scripts, which also
|
|
# exclude @multica/mobile.
|
|
run: pnpm exec turbo build typecheck lint --filter='!@multica/docs' --filter='!@multica/mobile'
|
|
|
|
frontend-test:
|
|
needs: changes
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
TURBO_CACHE_DIR: .turbo/cache
|
|
steps:
|
|
- name: Checkout
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup pnpm
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Setup Node.js
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
run: pnpm install
|
|
|
|
- name: Resolve runtime for cache key
|
|
id: runtime
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
run: echo "node=$(node --version)" >> "$GITHUB_OUTPUT"
|
|
|
|
# See frontend-build for the key strategy. These entries are tiny (~60KB
|
|
# measured): `test` declares no outputs, so turbo caches exit codes and
|
|
# logs rather than artifacts -- yet a hit still skips the whole suite,
|
|
# which is the single most expensive task in the graph.
|
|
- name: Restore turbo cache
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: .turbo/cache
|
|
key: turbo-test-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-${{ github.sha }}
|
|
restore-keys: |
|
|
turbo-test-${{ runner.os }}-${{ runner.arch }}-${{ steps.runtime.outputs.node }}-
|
|
|
|
- name: Test
|
|
if: ${{ needs.changes.outputs.frontend == 'true' }}
|
|
# Same filter rationale as frontend-build. Type errors are not this
|
|
# job's responsibility -- frontend-build owns the `typecheck` task.
|
|
# `test` reaches dependency sources through the hash-only
|
|
# `^cache-inputs` edge (see turbo.json), so no `tsc` runs here.
|
|
run: pnpm exec turbo test --filter='!@multica/docs' --filter='!@multica/mobile'
|
|
|
|
# Aggregate gate. `frontend` is the status-check name the repository's
|
|
# branch rules refer to, so it has to survive the split above: this job
|
|
# keeps reporting under that name and simply fails when either half fails.
|
|
# It also inherits the old contract that the check goes green (rather than
|
|
# staying pending) on PRs the path filter excluded — both halves succeed
|
|
# trivially in that case because every step is gated off.
|
|
frontend:
|
|
needs: [frontend-build, frontend-test]
|
|
# `!cancelled()` rather than `always()`: a run superseded by a newer push
|
|
# is cancelled by the concurrency group above, and there is no reason to
|
|
# spend a runner reporting a verdict nobody will read.
|
|
if: ${{ !cancelled() }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check frontend job results
|
|
env:
|
|
BUILD_RESULT: ${{ needs.frontend-build.result }}
|
|
TEST_RESULT: ${{ needs.frontend-test.result }}
|
|
run: |
|
|
echo "frontend-build: $BUILD_RESULT"
|
|
echo "frontend-test: $TEST_RESULT"
|
|
if [ "$BUILD_RESULT" != "success" ] || [ "$TEST_RESULT" != "success" ]; then
|
|
echo "::error::frontend validation failed"
|
|
exit 1
|
|
fi
|
|
|
|
backend:
|
|
runs-on: ubuntu-latest
|
|
services:
|
|
postgres:
|
|
image: pgvector/pgvector:pg17
|
|
env:
|
|
POSTGRES_DB: multica
|
|
POSTGRES_USER: multica
|
|
POSTGRES_PASSWORD: multica
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd "pg_isready -U multica -d multica"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 20
|
|
redis:
|
|
image: redis:7-alpine
|
|
ports:
|
|
- 6379:6379
|
|
options: >-
|
|
--health-cmd "redis-cli ping"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 10
|
|
env:
|
|
DATABASE_URL: postgres://multica:multica@localhost:5432/multica?sslmode=disable
|
|
# Wires up the RedisLocalSkill*_test.go suite. Distinct from REDIS_URL
|
|
# (which would flip the server binary itself onto the Redis-backed
|
|
# realtime relay + request stores); the tests talk to this Redis
|
|
# directly so they run alongside the Postgres-backed suite.
|
|
REDIS_TEST_URL: redis://localhost:6379/1
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: "1.26.1"
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Setup Helm
|
|
uses: azure/setup-helm@v4
|
|
|
|
- name: Test Helm chart
|
|
run: bash scripts/helm-config.test.sh
|
|
|
|
- name: Build
|
|
run: cd server && go build ./...
|
|
|
|
- name: Run migrations
|
|
run: cd server && go run ./cmd/migrate up
|
|
|
|
- name: Verify Go test wrapper
|
|
run: bash scripts/test-go.test.sh
|
|
|
|
- name: Test
|
|
run: bash scripts/test-go.sh --race
|
|
|
|
windows-execenv:
|
|
# The environment-preparation deadline owns a process tree, not just a Go
|
|
# process. This Windows runtime test verifies Job Object cancellation kills
|
|
# a delayed descendant before an immediate retry can reuse the same root.
|
|
runs-on: windows-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: "1.26.1"
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Test Windows execution-environment isolation
|
|
working-directory: server
|
|
# Keep this job scoped to the runtime regression it exists to prove.
|
|
# The package's legacy OpenClaw HOME tests are not Windows-safe and are
|
|
# outside this PR; the normal backend job still runs the full package.
|
|
run: go test ./internal/daemon/execenv -run '^TestPrepareIsolated_WindowsKillsDescendantBeforeRetry$' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows agent launcher argv/stdin handling
|
|
working-directory: server
|
|
# Agent prompts must never reach a Windows launcher through argv: the
|
|
# official cursor-agent.ps1 ends in `& node.exe index.js $args`, and
|
|
# PowerShell re-serialises $args onto the child command line. Under
|
|
# Legacy native argument passing (powershell.exe 5.1, pwsh <= 7.2) a
|
|
# prompt holding embedded quotes is re-tokenised and fragments like
|
|
# `-X` become flags (#5649). Only a real PowerShell host proves this,
|
|
# so it cannot live in the ubuntu backend job. Scoped to the launcher
|
|
# tests, which are windows-tagged and therefore run nowhere else today;
|
|
# the backend job still runs the full package on Linux.
|
|
# -v so a silent skip (no PowerShell host resolved, or a -run pattern
|
|
# that stops matching) is visible in the log instead of passing as "ok".
|
|
run: go test ./pkg/agent -v -run '^(TestCursorExecutePromptSurvivesPowerShellShim|TestPlatformCursorInvocation|TestPlatformCopilotInvocation|TestPlatformPiInvocation)' -count=1 -timeout=5m
|
|
|
|
- name: Test bounded Codex cleanup with inherited stdout descendant
|
|
working-directory: server
|
|
# Windows cannot prove whole-tree termination without a Job Object,
|
|
# but a descendant holding inherited stdout must never keep Result
|
|
# blocked forever. -v makes RUN/PASS evidence explicit in CI logs.
|
|
run: go test ./pkg/agent -v -run '^TestCodexWindowsInheritedStdoutDescendantCleanupIsBounded$' -count=1 -timeout=5m
|
|
|
|
- name: Test Windows OpenClaw npm shim interpreter resolution
|
|
working-directory: server
|
|
# #6061: every OpenClaw task failed execenv prep on a Windows host with
|
|
# a bare `exit status 1` and no stderr. A batch shim resolves and runs
|
|
# fine while the `node` it re-execs is unreachable, and npm's real
|
|
# template prefers a co-located node.exe over PATH — none of which can
|
|
# be proven without a real cmd.exe host. These tests pin: the positive
|
|
# control (node on PATH → success), that a missing node surfaces
|
|
# cmd.exe's own stderr (the first run of this job disproved #6061's
|
|
# premise that it does not), that a genuinely silent shim DOES reach the
|
|
# new diagnostic, that a co-located interpreter is credited, that a
|
|
# context timeout is not misdiagnosed as a missing interpreter, and that
|
|
# TEMP/TMP are NOT load-bearing (the originally reported root cause,
|
|
# since retracted upstream).
|
|
# Scoped to the windows-tagged shim tests — the package's legacy
|
|
# OpenClaw HOME tests are not Windows-safe; the backend job still runs
|
|
# the full package plus the cross-platform half on Linux.
|
|
# -v so a skip (no node on the runner) is visible instead of passing
|
|
# silently as "ok".
|
|
run: go test ./internal/daemon/execenv -v -run '^TestWindowsOpenclawShim' -count=1 -timeout=5m
|
|
|
|
- name: Build Windows CLI helper entrypoint
|
|
working-directory: server
|
|
run: go build ./cmd/multica
|
|
|
|
installer:
|
|
# Stub-driven shell tests for scripts/install.sh. Kept off the heavy
|
|
# backend job so installer regressions surface independently, and
|
|
# exercised on macOS too because the installer targets macOS/Homebrew
|
|
# and `tar` / `sed` / `mktemp` differ between BSD and GNU userlands.
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest]
|
|
runs-on: ${{ matrix.os }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Test shell installers
|
|
run: bash scripts/install.test.sh
|