diff --git a/AGENTS.md b/AGENTS.md index d27811370fd..09ed6423ac4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -102,20 +102,6 @@ - Pure test additions/fixes generally do **not** need a changelog entry unless they alter user-facing behavior or the user asks for one. - Mobile: before using a simulator, check for connected real devices (iOS + Android) and prefer them when available. -### Local full-CI one-shot record (2026-02-25) - -- Ran local parity sweep for GitHub CI workflows (`workflow-sanity.yml`, `ci.yml`, `install-smoke.yml`, `sandbox-common-smoke.yml`) via `/tmp/run-openclaw-local-ci.sh`. -- Artifacts: `/tmp/openclaw-local-ci-20260225-221535.summary`, `/tmp/openclaw-local-ci-20260225-221535.log`. -- Result: `PASS=15`, `FAIL=15`, `SKIP=1` (`ci:secrets-zizmor` skipped because no workflow-file diff vs `origin/main`). -- `ci.yml` jobs with `if: false` (`deadcode`, `ios`) are disabled in GitHub CI and were not executed. -- Main local blockers seen in this run: - - `ci:check`: formatting failure in `src/daemon/launchd.ts`. - - `ci:checks-node-test` and `ci:macos-ts-tests`: test failures plus Node OOM during full `pnpm test`. - - `ci:skills-python-*` / `ci:secrets-*`: Homebrew Python PEP668 (`externally-managed-environment`) and missing `pre-commit`/`detect-secrets`. - - `ci:macos-swift*`: missing `swiftlint`/`swiftformat` and Swift `6.2.0` required vs local `6.1.0`. - - `ci:android-*`: Android SDK not configured (`ANDROID_HOME` / `apps/android/local.properties`). - - `sandbox-common-smoke:common-image`: passed after running the workflow command directly (temp script had unescaped `$u` with `set -u`). - ## Commit & Pull Request Guidelines **Full maintainer PR workflow (optional):** If you want the repo's end-to-end maintainer workflow (triage order, quality bar, rebase rules, commit/changelog conventions, co-contributor policy, and the `review-pr` > `prepare-pr` > `merge-pr` pipeline), see `.agents/skills/PR_WORKFLOW.md`. Maintainers may use other workflows; when a maintainer specifies a workflow, follow that. If no workflow is specified, default to PR_WORKFLOW. diff --git a/CHANGELOG.md b/CHANGELOG.md index b890896f0d3..21e75f9ab6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Daemon/macOS launchd: forward proxy env vars into supervised service environments, switch LaunchAgent keepalive policy to crash-only with throttling, and harden restart sequencing to `print -> bootout -> wait old pid exit -> bootstrap -> kickstart`. (#27276) thanks @frankekn. - Android/Node invoke: remove native gateway WebSocket `Origin` header to avoid false origin rejections, unify invoke command registry/policy/error parsing paths, and keep command availability checks centralized to reduce dispatcher/advertisement drift. (#27257) Thanks @obviyus. - CI/Windows: shard the Windows `checks-windows` test lane into two matrix jobs and honor explicit shard index overrides in `scripts/test-parallel.mjs` to reduce CI critical-path wall time. (#27234) Thanks @joshavant. diff --git a/src/daemon/launchd.test.ts b/src/daemon/launchd.test.ts index d302a15de64..7465666a158 100644 --- a/src/daemon/launchd.test.ts +++ b/src/daemon/launchd.test.ts @@ -12,6 +12,7 @@ import { const state = vi.hoisted(() => ({ launchctlCalls: [] as string[][], listOutput: "", + printOutput: "", bootstrapError: "", dirs: new Set(), files: new Map(), @@ -36,6 +37,9 @@ vi.mock("./exec-file.js", () => ({ if (call[0] === "list") { return { stdout: state.listOutput, stderr: "", code: 0 }; } + if (call[0] === "print") { + return { stdout: state.printOutput, stderr: "", code: 0 }; + } if (call[0] === "bootstrap" && state.bootstrapError) { return { stdout: "", stderr: state.bootstrapError, code: 1 }; } @@ -72,6 +76,7 @@ vi.mock("node:fs/promises", async (importOriginal) => { beforeEach(() => { state.launchctlCalls.length = 0; state.listOutput = ""; + state.printOutput = ""; state.bootstrapError = ""; state.dirs.clear(); state.files.clear(); @@ -224,6 +229,42 @@ describe("launchd install", () => { expect(bootstrapIndex).toBeLessThan(kickstartIndex); }); + it("waits for previous launchd pid to exit before bootstrapping", async () => { + const env = createDefaultLaunchdEnv(); + state.printOutput = ["state = running", "pid = 4242"].join("\n"); + const killSpy = vi.spyOn(process, "kill"); + killSpy + .mockImplementationOnce(() => true) + .mockImplementationOnce(() => { + const err = new Error("no such process") as NodeJS.ErrnoException; + err.code = "ESRCH"; + throw err; + }); + + vi.useFakeTimers(); + try { + const restartPromise = restartLaunchAgent({ + env, + stdout: new PassThrough(), + }); + await vi.advanceTimersByTimeAsync(250); + await restartPromise; + expect(killSpy).toHaveBeenCalledWith(4242, 0); + const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501"; + const label = "ai.openclaw.gateway"; + const bootoutIndex = state.launchctlCalls.findIndex( + (c) => c[0] === "bootout" && c[1] === `${domain}/${label}`, + ); + const bootstrapIndex = state.launchctlCalls.findIndex((c) => c[0] === "bootstrap"); + expect(bootoutIndex).toBeGreaterThanOrEqual(0); + expect(bootstrapIndex).toBeGreaterThanOrEqual(0); + expect(bootoutIndex).toBeLessThan(bootstrapIndex); + } finally { + vi.useRealTimers(); + killSpy.mockRestore(); + } + }); + it("shows actionable guidance when launchctl gui domain does not support bootstrap", async () => { state.bootstrapError = "Bootstrap failed: 125: Domain does not support specified action"; const env = createDefaultLaunchdEnv();