This repository builds a Windows MSIX package containing:
- one .NET 10 NativeAOT launcher exposed through separate packaged
openclawandclawctlapplication identities and app execution aliases; - a pinned, verified build of
openclaw/openclaw; - the official Node.js archive matching the upstream build's runtime version and the package architecture.
The package is independent from the
OpenClaw Windows Node and Companion
and uses a separate OpenClaw.Gateway package identity. Both packages use the
OpenClaw Foundation publisher metadata established for OpenClaw's Windows
packages.
- Windows 11 on a build that supports isolated agent sessions, on x64 or ARM64.
OpenClaw always runs inside an isolated session, so a machine that cannot host
one is not supported:
clawctl setupandopenclawboth fail with a message naming this requirement and the diagnostic log path. Install the latest Windows updates (Settings > Windows Update), or install a newer Windows version, and runclawctl setupagain. - Developer Mode is required only for the local loose-layout development loop, not for the signed MSIX.
Both application identities activate the same packaged openclaw.exe. The
launcher recovers either the package-qualified application identity or the alias
used to start it from the native process command line and selects one of two
deliberately separate surfaces.
openclaw is a transparent launcher for the bundled OpenClaw CLI. It does not
own package-management commands. Every argument, including an empty argument
list, is forwarded unchanged to node openclaw.mjs, and the launcher returns
the exact child exit code.
openclaw runs only inside the isolated agent session recorded by
clawctl setup. It does not run OpenClaw on the host, and it fails rather than
falling back when the recorded session is missing, owned by another
installation, or unavailable.
Before launching, the host resolves the bundled Node.js executable previously
prepared by clawctl setup and checks its PE product version and executable
architecture against the packaged archive without a separate Node.js process.
The runtime directory is prepended
to the child's PATH so Node.js, npm, and npx subprocesses use the bundled
tools without changing the user's environment.
The expanded OpenClaw application is installed read-only inside the MSIX.
After resolving Node.js, the launcher confirms that packaged
app\openclaw.mjs exists and executes it directly. It does not extract, hash,
copy, repair, or otherwise change package files at runtime.
Every OpenClaw child process runs with
OPENCLAW_SUPERVISOR_MODE=external,
OPENCLAW_SERVICE_REPAIR_POLICY=external, and
OPENCLAW_NO_AUTO_UPDATE=1. It also reports the Windows Gateway session mode
through the process-stable CLAWCTL_GATEWAY_ISOLATION=enabled environment
variable. OpenClaw always runs inside the isolated session, so this value is
always enabled.
These values declare external lifecycle ownership, prevent doctor-owned service
repair, disable configured background auto-updates, and expose diagnostic
isolation status without claiming independent attestation. The selected OpenClaw runtime honors external supervisor mode by refusing native service
mutation and OpenClaw self-update with guidance to use the external supervisor's
workflow. This behavior belongs to upstream OpenClaw; the launcher does not
reserve, reject, or rewrite upstream command arguments.
OpenClaw inherits the terminal's working directory; the launcher does not make
the read-only application directory the workspace.
clawctl owns setup and the isolated-session operations:
| Command | Behavior |
|---|---|
clawctl setup |
Confirm packaged app\openclaw.mjs exists, provision or reuse the owned isolated session, and install the bundled Node.js runtime in the agent profile. It also configures gateway sign-in recovery without starting a gateway. On a machine that cannot host a session it fails with the Windows requirement described under Requirements. |
clawctl setup --fresh [--force] |
Remove this installation's owned session and package-local state, then run setup again. Without --force, incomplete external cleanup stops before local state is erased. --force is valid only with --fresh; it preserves an explicit warning when cleanup of owned external resources cannot be confirmed, but still stops if bounded local deletion fails. |
clawctl status |
Report the recorded isolated session, installed Node.js runtime, gateway, and sign-in recovery state without provisioning or replacing the session. It asks the backend to start the recorded provision as its status probe, so it is not a passive diagnostic. Use clawctl gateway-service status to inspect the gateway alone. |
clawctl teardown --force |
Confirm deletion, then stop and deprovision the owned session and remove its data and setup state. The MSIX remains installed. |
clawctl pwsh |
Open an interactive PowerShell session inside the agent session. |
clawctl collect-logs [--output <path>] |
Create a redacted host-and-agent diagnostics ZIP. |
clawctl gateway-service start |
Start the OpenClaw gateway in the isolated session and wait for it to listen. Requires setup. |
clawctl gateway-service status |
Inspect the gateway without starting it. |
clawctl gateway-service stop |
Stop the gateway while retaining the session and its data. |
clawctl --version |
Print the packaged launcher version. |
Bare clawctl, clawctl -h, and clawctl --help print help without changing
state. clawctl setup --help prints help for that command alone. Parsing,
usage errors, and completion come from
System.CommandLine,
while help is rendered by clawctl itself from the live command tree, so a
command added to the parser is documented without a separate help edit.
Invalid management input is rejected with exit code 1 and a parse diagnostic
on standard error; no readiness check runs.
All non-interactive commands accept --json and emit a versioned JSON document
on standard output. Human diagnostics remain on standard error, and command
exit codes do not change. clawctl pwsh --json is rejected because the command
hands the terminal to an interactive shell.
Interactive terminals use color for headings and status marks. --no-color,
the NO_COLOR environment variable, redirected output, and CI disable color;
FORCE_COLOR enables it for redirected output or CI unless color was
explicitly disabled. JSON output never contains terminal escape sequences.
clawctl gateway-service start waits for the gateway to bind rather than
returning as soon as the process exists, because a process without a listener
is not a usable gateway. It reports each stage as it happens — a spinner on an
interactive terminal, one line per stage anywhere else — and narrates nothing
at all under --json, so standard output carries exactly one document.
The wait has a fixed budget. A gateway still coming up when the budget is spent
is reported as starting rather than failed, and clawctl gateway-service status
will show it once it binds.
clawctl reports the gateway port only when it can identify that listener
unambiguously:
clawctl gateway-service start
Gateway: ✓ listening
Port: 18789
Token: openclaw gateway auth-token --show
OpenClaw owns the endpoint configuration, including TLS and a custom Control UI
base path. The Windows package therefore does not construct an HTTP URL that
might contradict that configuration. It reports no port when multiple
unclassified listeners remain. JSON follows the same rule: gateway.port is
present only when identified, and no URL is promised.
Reaching the Control UI needs the shared gateway token, which
openclaw gateway auth-token --show reveals. --json carries the identified
port but not that command: a script should run it rather than parse a
suggestion.
Help and version requests take precedence over the rest of the command line.
clawctl --version bogus reports the build identity and exits 0 rather than
reporting bogus, because the version request is satisfied before the
remaining arguments are validated.
clawctl --version reports the package version and packaging-repository
commit alongside the bundled OpenClaw payload version and its commit:
clawctl 0.0.0.1
Package: 0.0.0.1 (bfcb5ba73e7ea3e88ceed8c326e58e5baadc3191)
Payload: 2026.8.2 (0965053fe6b9341776df147a6934b7485c60b5ca)
Each commit is the one that produced the version it follows, and is muted so the version stays the value a reader compares.
clawctl --version --json reports the same identity as a versioned document,
so a support or deployment script can collect it without parsing prose:
{
"ok": true,
"schemaVersion": 1,
"command": "version",
"package": { "version": "0.0.0.1", "commit": "bfcb5ba…" },
"payload": { "version": "2026.8.2", "commit": "0965053…" }
}Those four values are compiled into the binary as constants by the build that
produces the package, so the report cannot drift from the payload it shipped
with and costs no file or process access at startup. Build-MSIX.ps1 supplies
the versions and commits it also records in msix-metadata.json; an ordinary
build falls back to the pin recorded in release-policy.json, and reports
unknown for a value no build supplied. The report never comes from the entry
assembly, so it stays correct when the launcher is hosted by another process.
Response-file expansion is disabled. A leading @ has no meaning to clawctl
and is reported as an unrecognized argument rather than read from disk.
These parser conveniences belong to clawctl only. openclaw forwards every
argument to the OpenClaw CLI verbatim, so a leading @ or a directive-shaped
token reaches that CLI uninterpreted.
Commands such as doctor, gateway, and uninstall belong to the OpenClaw
CLI and must be invoked through openclaw.
clawctl setup provisions an explicitly owned agent session and extracts the
architecture-specific runtime archive from the immutable MSIX into that agent's
writable LocalState:
%LOCALAPPDATA%\Packages\<package-family>\LocalState\OpenClaw\NodeJS\node-v<version>-win-<architecture>.
Extraction is idempotent, versioned, and serialized across concurrent setup
processes, including different Windows sessions. Setup validates existing
runtimes before reuse, replaces invalid runtimes, and validates extraction
before publishing it. It does not prepare the invoking user's host runtime,
because nothing runs on the host.
Run setup before using openclaw, clawctl pwsh, or gateway-service start.
There is no session-free mode: openclaw runs inside the session recorded by
setup, and both entry points fail with the same message on a machine that
cannot host one. See
MXC compatibility evidence for the
session model, gateway health criteria, and diagnostics limits.
The launcher places Node.js in a Windows job configured with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. The launcher remains alive while Node.js
runs; if the launcher exits or is terminated, Windows terminates Node.js and
its child processes when the job handle closes.
Prepare the bundled runtime once, then use openclaw:
clawctl setup
openclawWhen an MSIX update changes the bundled Node.js version, run clawctl setup
again before launching OpenClaw. Previously extracted versions are left in
place so an update does not remove a running process's runtime.
.github\workflows\gateway-msix.yml resolves an explicit OpenClaw ref before
building. Pull-request and main push runs use the pinned commit configured in
both:
workflow_dispatch.inputs.openclaw_ref.default;- the non-manual fallback in
env.OPENCLAW_REF.
Changing only the workflow-dispatch default does not change automatic builds.
For a one-time override, run Build OpenClaw Gateway MSIX manually and
provide a tag, branch, or preferably a full 40-character commit SHA in
openclaw_ref. Payload composition validates that the selected OpenClaw
runtime discovers the packaging-owned Windows Launcher plugin in its
default-disabled state, then explicitly enables only that plugin in an isolated
temporary validation profile before using OpenClaw's runtime inspection pass to
validate its required read-only route shape. The temporary profile is removed
after inspection and does not modify user configuration. Incompatible older
refs fail instead of producing a package with an unvalidated plugin.
The source build uses that revision's .github/actions/setup-node-env action
to select Node.js and pnpm. Its resolved Node.js version is recorded in
source.json, reused for both Windows payload builds, and carried in
payload-metadata.json. Package composition downloads that exact version;
the launcher derives its runtime version and LocalState path from the bundled
archive name. There is no separate packaging-side Node.js version pin or
runtime-support policy.
Non-official workflows cache the packed OpenClaw tarball by its resolved upstream commit. They also cache each architecture's Windows dependency tree by the resolved commit, tarball SHA-256, Node.js version, and payload-build script. A tarball cache hit still verifies the recorded commit and SHA-256; a dependency-tree hit still runs every payload validation and smoke test. Official-signing workflows bypass both caches and always rebuild upstream source and Windows dependencies.
The payload artifact records the requested ref and resolved upstream commit in
payload-metadata.json. That build-only file is not embedded in the MSIX.
msix-metadata.json records both the packaging repository commit and bundled
OpenClaw commit, while embedded payload-files.json records every packaged
application file's path, length, and SHA-256.
release-policy.json records the immutable OpenClaw commit and Gateway tag
approved for official signing, plus an independent MSIX packaging revision.
Updating that
policy requires a reviewed repository change. Official signing runs only from
main and verifies the workflow input, policy-approved package version, both
architecture metadata files, both MSIX hashes, the embedded manifests, and
every file against the embedded application inventory. It also byte-compares
the bundle's embedded packages with those authorized standalone packages before
requesting Azure credentials.
dotnet restore .\OpenClaw.Gateway.MSIX.slnx
dotnet test .\OpenClaw.Gateway.MSIX.slnx `
--configuration Release `
--no-restorescripts\Build-Payload.ps1 npm-installs an OpenClaw package into an expanded,
architecture-specific application tree. It validates the Gateway and Control UI
build identities on the installed tree, including reused staged installs, then
provisions the packaging-owned Windows Launcher plugin into the payload copy's
bundled plugin directory. Its internal package, path, and plugin ID remain
gateway-isolation. The plugin is disabled by default, so normal installs do
not activate it, register its route, or show the Windows Launcher tab. When
explicitly enabled for validation or by the future launcher command
implementation, it adds the read-only tab to the Control group and serves it
through an authenticated, sandboxed plugin route. It reads only the launch-time
CLAWCTL_GATEWAY_ISOLATION value and registers no mutation RPC or process
control.
The page preserves the planned clawctl gateway-isolation enable|disable
command and Copy control for the paired launcher command update. This package
does not register those clawctl commands yet, so the page explicitly tells
users to run the command only after that support is installed.
The screenshots attached to the pull request are design and behavior proof captured with the plugin explicitly enabled in an isolated validation profile; they do not represent the default-disabled state of a normal install.
Full selected-theme cohesion requires the generic plugin-frame theme forwarding
merged by
openclaw/openclaw#145409.
The current workflow remains on the release-approved OpenClaw v2026.9.4
baseline (3a9d69db306cd7f081e06254cb89c4bcc14a7107) while this plugin is disabled by
default. That baseline packages and inspects the plugin safely but does not
forward selected Control UI themes into plugin frames. The future launcher
enablement change must also advance and qualify the runtime to the merged theme
forwarding commit f65ecca89667b8a55d9f88d76c487f0a0ab11da8 or newer. Until
then, the page uses the browser or operating system light/dark preference with
a safe built-in palette.
scripts\Build-MSIX.ps1 downloads the official Node.js archive matching the
payload's recorded build version and architecture, copies both inputs into
package content, rejects Node.js inside the application payload, creates a
per-file inventory, and then creates an unsigned NativeAOT MSIX.
scripts\Build-LocalMSIX.ps1 can reuse a successful workflow payload or a
local payload directory. -NodeArchivePath can supply an already-downloaded
archive, but its version and architecture must match the payload metadata.
To go from a clean checkout to a registered, runnable package:
.\scripts\Deploy-LocalPackage.ps1This is the development inner loop. It does not build, sign, or install an
MSIX. It acquires the payload and the bundled Node.js runtime, publishes the
NativeAOT launcher, assembles a Developer Mode layout under
artifacts\local-package, registers it with Add-AppxPackage -Register, and
runs clawctl setup so openclaw is immediately usable.
The command is idempotent: re-running with nothing changed reports that the package is already up to date and does nothing, and re-running after a source or payload change rebuilds only what changed. The expanded application is linked into the layout rather than copied, so repeat runs neither re-download nor duplicate hundreds of megabytes.
| Option | Behavior |
|---|---|
-RefreshPayload |
Download the payload again; the previous one is kept until the new one registers successfully |
-PayloadRunId <id> |
Use a specific successful workflow run, reusing a matching cached payload |
-PayloadDirectory <path> |
Read a prepared payload directly, with no GitHub access and no modification; pass it on every run |
-Architecture x64 / arm64 |
Select the architecture; it must be runnable on this device |
-ReplaceExistingInstall |
Remove a conflicting MSIX-installed package first (see below) |
-SkipSetup |
Register without extracting the Node.js runtime |
-Force |
Re-register even when nothing changed |
-Unregister |
Remove the local registration, preserving app data and caches |
Requires Developer Mode, which the script checks before doing any work.
It cannot coexist with an MSIX-installed OpenClaw.Gateway. Windows
refuses to replace a packaged install with a local layout, and it cannot
preserve that package's app data across the switch, so the script stops and
explains rather than removing anything implicitly. Pass
-ReplaceExistingInstall to accept that trade.
Run -Unregister before installing a released package. Windows will not
replace a loose registration with a packaged install: Add-AppxPackage fails
with 0x80073CFB, reporting that an unpackaged version is already installed
and a packaged version cannot replace it. This is the same mutual exclusion as
above, in the other direction, and it applies regardless of version. Unregister
first, then install the release:
.\scripts\Deploy-LocalPackage.ps1 -Unregister
Add-AppxPackage -Path .\OpenClawGateway-0.0.0.0-x64.msixThe registered package reads its files from the repository. Deleting
artifacts\local-package, moving the checkout, or deleting the worktree breaks
the registration until the command runs again; -Unregister first if you plan
to remove the checkout. Local builds are unsigned development artifacts and are
never official-signing inputs.
Normal pull-request and push workflows publish unsigned packages for validation. Manual runs support three signing modes:
unsignedaccepts any OpenClaw branch, tag, or commit and publishes unsigned MSIX packages;testaccepts any OpenClaw ref and publishes MSIX packages signed with a temporary self-signed certificate plus the public.cerneeded for local installation;officialrequires the approved immutable commit fromrelease-policy.json, may run only frommain, and publishes the signed packages as permanent assets on a GitHub Release named by the policy.
Official signing uses the protected release-signing environment, Azure OIDC,
and the existing OpenClaw Artifact Signing account and certificate profile.
Test-signing private keys are generated only on the temporary GitHub runner
and are deleted before artifacts are uploaded. No signing secret or private
key is stored in the repository.
Official releases derive their GitHub tag and four-part numeric MSIX identity
from gatewayTag and msixRevision in release-policy.json. The GitHub tag is
<gateway-tag>-msix.<revision>. The MSIX identity is
year.month.VVPN.0: VV is the two-digit monthly Gateway release sequence,
P is the Gateway correction digit, and N is the MSIX rebuild digit. The
digits are packed numerically into the third component, so leading zeroes are
not written.
| Gateway tag | MSIX revision | GitHub release tag | MSIX version |
|---|---|---|---|
v2026.7.1 |
0 |
v2026.7.1-msix.0 |
2026.7.100.0 |
v2026.7.1 |
1 |
v2026.7.1-msix.1 |
2026.7.101.0 |
v2026.7.1-2 |
0 |
v2026.7.1-2-msix.0 |
2026.7.120.0 |
v2026.7.2 |
0 |
v2026.7.2-msix.0 |
2026.7.200.0 |
v2026.7.12 |
0 |
v2026.7.12-msix.0 |
2026.7.1200.0 |
Gateway release sequences must be 1 through 99. The unsuffixed Gateway tag
uses correction digit 0; correction suffixes -2 through -9 use their
numeric suffix. A -1 suffix remains rejected to match the Gateway release-tag
contract. Set msixRevision from 0 through 9, starting at 0 for each
Gateway tag and incrementing it only when that exact Gateway tag is repackaged.
Decimal place value guarantees Gateway release > Gateway correction > MSIX
rebuild while keeping every component at four digits or fewer and reserving the
fourth component as 0 for Microsoft Store submission.
To prepare an official release, update these policy inputs together in a reviewed pull request:
gatewayTagto the stable upstream Gateway tag;approvedCommitto the immutable commit resolved from that tag;payloadPackageVersionto the version reported by the pinned payload;msixRevisionto0, or increment it for a packaging-only rebuild of the same Gateway tag;- the workflow's
openclaw_refdefault and non-manual fallback to the sameapprovedCommit.
After that pull request merges, manually run Build OpenClaw Gateway MSIX on
main with openclaw_ref set to the approved commit and signing_mode set to
official. The workflow derives the package version and release tag, creates
the tag in this repository, and publishes a GitHub Release with generated
release notes. Each release contains a signed, multi-architecture
OpenClawGateway-<version>.msixbundle as the recommended download, plus signed
OpenClawGateway-<version>-x64.msix and
OpenClawGateway-<version>-arm64.msix packages for architecture-specific
deployment. The duplicate GitHub Actions artifacts remain short-lived transport
and diagnostic copies.
The same identity can be used for direct distribution and Microsoft Store
submission; the fourth component is always 0.
The signed v0.0.0.0 and v0.0.0.1 proof releases are not production version
identities, but they are retained as transition baselines. The latest production
release is also retained as a migration baseline. Pull requests that change
release versioning download the hash-pinned standalone x64 and recommended
.msixbundle assets, install each one on a clean GitHub-hosted Windows runner,
upgrade it in place through the same delivery format, and
verify that the package family remains stable and a LocalState marker is
retained. The gate also proves fresh installation of both the standalone and
bundle candidates. It refuses to run when an OpenClaw Gateway package is
already registered and removes only packages installed by that test
invocation. It temporarily trusts the ephemeral test-signing certificate in
the local-machine Trusted People store, as required by Windows deployment, and
removes that certificate in finally. The resulting JSON evidence is retained
as a workflow artifact for 90 days. Future versioning schemes must keep this
transition gate green or explicitly document and obtain approval for a
breaking reset.
An .msixbundle is a single installable container for the x64 and ARM64 MSIX
packages; Windows selects the package appropriate for the device. An
.appinstaller file is separate update-channel metadata rather than an
alternative package format. This repository does not publish one yet, so GitHub
Release installs do not opt devices into automatic update checks.
The release-signing GitHub environment must define these environment
variables (they are identifiers, not credentials):
AZURE_CLIENT_ID: application (client) ID of the dedicatedopenclaw-windows-msix-signingEntra application;AZURE_TENANT_ID: Entra tenant ID;AZURE_SUBSCRIPTION_ID: Azure subscription containing the signing resource.
Do not create an AZURE_CLIENT_SECRET. The sign-msix job requests a
short-lived Azure token with GitHub OIDC. The Entra application must have a
federated identity credential with:
- issuer:
https://token.actions.githubusercontent.com; - subject:
repo:openclaw@252820863/openclaw-windows-packaging@1347889239:environment:release-signing; - audience:
api://AzureADTokenExchange.
This repository was created after GitHub's immutable OIDC subject rollout, so
the subject includes the organization and repository IDs. The older mutable
repo:openclaw/openclaw-windows-packaging:... form will not match its tokens.
The service principal must have Artifact Signing Certificate Profile Signer
on the openclaw certificate profile (or a containing scope). The workflow
uses account openclaw, certificate profile openclaw, and endpoint
https://eus.codesigning.azure.net/. The expected public certificate subject
is recorded in release-policy.json.
| Data | Default path |
|---|---|
| OpenClaw application files | Read-only MSIX package app directory |
| Bundled Node.js archive | Read-only MSIX package runtime directory |
| Extracted Node.js runtime | %LOCALAPPDATA%\Packages\<package-family>\LocalState\OpenClaw\NodeJS\node-v<version>-win-<architecture> |
| OpenClaw configuration and user state | %USERPROFILE%\.openclaw |
| Launcher diagnostics | %LOCALAPPDATA%\Packages\<package-family>\LocalState\OpenClawGatewayMSIX\Logs\openclaw.log |
OpenClaw application files are owned and serviced by Windows as part of the
immutable MSIX installation. OpenClaw user state remains outside the package.
Updating or removing the MSIX does not automatically delete that state or stop
a running Gateway. Use OpenClaw's documented
openclaw uninstall flow before
removing the MSIX.
The payload build emits an expanded npm-installed application tree.
Build-MSIX.ps1 rejects Node.js from that tree, copies it into package content,
and records every application file's path, length, and SHA-256 in
payload-files.json. It separately validates and hashes the pinned Node.js
archive. Package construction verifies both inputs against the generated MSIX.
Official signing authorization repeats the application inventory and Node.js
archive validation before requesting signing credentials.
At runtime, Windows' MSIX package integrity and read-only enforcement remains
the trust boundary for the application and archive. clawctl setup extracts
the archive into versioned package LocalState; openclaw launches the packaged
app\openclaw.mjs directly with that extracted executable. Neither command
hashes or walks the expanded application inventory.
The longer-term design is to run the Gateway payload in a dedicated isolated agent session rather than the interactive session where the human user is logged in. This will provide a boundary similar in purpose to running the Gateway in WSL, using the forthcoming isolated-session capabilities. That isolation is not provided by the current MSIX implementation.
This is an independent public implementation in the OpenClaw ecosystem, informed by the upstream OpenClaw and Windows Node projects rather than a source fork of either repository. See CONTRIBUTORS.md for acknowledgements and links to the contributor histories.