Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
381108a
Unify UI and sandbox state under the user .winapp directory
nmetulev Sep 18, 2026
684e78a
Keep winapp useful when global storage is restricted
nmetulev Sep 18, 2026
065d722
Fix update-notice CI coverage and review cleanup findings
nmetulev Sep 18, 2026
e62a0ff
Protect target state and classify storage failures precisely
nmetulev Sep 18, 2026
e75c664
Address independent review findings in restricted storage flows
nmetulev Sep 18, 2026
2098371
Merge branch 'main' into nmetulev-winapp-lock-directory
nmetulev Sep 21, 2026
4fe5756
Make UI timing tests assert deadline-aware behavior
nmetulev Sep 21, 2026
a1879fb
Merge branch 'main' into nmetulev-winapp-lock-directory
zateutsch Sep 21, 2026
dcac09d
Merge branch 'main' into nmetulev-winapp-lock-directory
nmetulev Sep 22, 2026
5ac0337
Merge branch 'main' into nmetulev-winapp-lock-directory
nmetulev Sep 22, 2026
dbec8ba
Make filesystem test fixtures independent of legacy path policy
nmetulev Sep 22, 2026
7e517e1
Preserve deployment snapshots during concurrent state reads
nmetulev Sep 22, 2026
e64d6ca
Merge branch 'main' into nmetulev-winapp-lock-directory
nmetulev Sep 22, 2026
7206c50
Merge branch 'main' into nmetulev-winapp-lock-directory
zateutsch Sep 22, 2026
0b5a05d
Publish target state with a single atomic Windows rename
nmetulev Sep 22, 2026
376699a
Merge branch 'main' into nmetulev-winapp-lock-directory
nmetulev Sep 22, 2026
000558b
Exercise MP4 completion with a short frame sequence
nmetulev Sep 22, 2026
79b663a
Validate UI coordination namespace before touching state
nmetulev Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/sandbox-execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ Developer Mode, and an inbound firewall rule. winapp does not stop an adopted in
or remove unrelated apps. There is **no silent host fallback**: a command requesting
Sandbox runs there or fails.

Host-side ownership and deployment records use [shared runtime state](usage.md#shared-runtime-state),
independently of the configured cache directory. That state must remain accessible;
an inaccessible ownership record is not treated as permission to start managing an
unrelated Sandbox. Disposable runtime caches can use
[current-directory fallback storage](usage.md#restricted-filesystem-access), but
that does not replace the shared ownership and lifecycle locks.

## Running and rebuilding

```powershell
Expand Down Expand Up @@ -358,6 +365,7 @@ copying a suggestion keeps it on the same execution target.
| `sandbox_agent_incompatible` | Follow the version error; upgrade the installed CLI using its installation method if requested, then close/retry only with consent |
| `sandbox_agent_busy` | Wait for another command to finish, then retry |
| `sandbox_terminated`, `sandbox_target_stale`, `sandbox_stale_handle` | Rerun the app and rediscover guest PIDs/windows |
| `sandbox_state_unavailable` | Fix the reported state path: use writable local storage without junctions or symbolic links, owned and accessible only by you (SYSTEM and Administrators are trusted). Parent directories must prevent other users from replacing it. Existing exposed state is not repaired or deleted; safely stop the affected Sandbox before replacing that state and its connection keys in a secure directory. |
| `sandbox_deployment_dirty`, `sandbox_transfer_interrupted` | Retry the deployment or transfer |
| `sandbox_runtime_provision_failed` | Resolve the named dependency or unsupported runtime configuration; see [Shared runtimes](#shared-runtimes) |
| `sandbox_package_conflict`, `sandbox_provisioned_package_conflict` | Follow the package-specific action; do not remove unrelated or inbox packages |
Expand Down
13 changes: 11 additions & 2 deletions docs/ui-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ other, dismiss a menu the other just opened, or move a target out from under a p
turn, with no setup and no way to switch it off, so two agents can never type into each other's
windows. Read-only commands keep running concurrently.

Coordination uses [shared runtime state](usage.md#shared-runtime-state), independently
of the configured cache directory. If that storage is inaccessible, read-only
observation can continue with a warning, but it does not participate in workflow
ordering. Mutations and captures still require coordination. See
[restricted filesystem access](usage.md#restricted-filesystem-access) for fallback
and diagnostic behavior.

**Continuity between commands is opt-in.** By default each command is a self-contained one-shot: it
waits its turn, does its work, and releases the desktop immediately. To keep the desktop across
several commands, give them all the same workflow id:
Expand Down Expand Up @@ -201,8 +208,10 @@ record a workflow driving an app. Two caveats:
recording and the command says so in its output; even same-workflow input will wait.

Errors you may see: `invalid_ui_workflow_id` (the variable is set but empty or over 256 characters),
`desktop_coordination_unavailable` (coordination state is unreadable and cannot be safely rebuilt, or
was written by a newer `winapp`), `queue_capacity_exceeded` (64 commands from **other** workflows are
`invalid_ui_lock_directory` (an explicit coordination path is invalid; correct or remove the override),
`desktop_coordination_unavailable` (coordination state is unreadable and cannot be safely rebuilt,
was written by a newer `winapp`, or its [storage path is untrusted](usage.md#restricted-filesystem-access)),
`queue_capacity_exceeded` (64 commands from **other** workflows are
already waiting — the limit counts live foreign waiters, not processes you have started, so entries
belonging to commands that have exited or been killed do not occupy a slot, and your own workflow's
commands queue behind each other rather than against this limit), `ui_turn_busy` (`yield` while your
Expand Down
119 changes: 116 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1771,7 +1771,7 @@ Search **WinUI** controls and samples for a working code example. WinUI-only: th
winapp find-ui "<query>" [options]
```

The Gallery, Toolkit, and Reactor corpora ship **inside the CLI**, so `find-ui` works with no network access — including on a first run in an agent sandbox or behind a corporate proxy that blocks `raw.githubusercontent.com`. When GitHub *is* reachable the CLI refreshes from it and caches the result per-user under `<global .winapp>/cache/find-ui`; the built-in corpus is only a floor, never a ceiling. Cached data is refreshed at most every 24 hours, or on demand with `--refresh`.
The Gallery, Toolkit, and Reactor corpora ship **inside the CLI**, so `find-ui` works with no network access — including on a first run in an agent sandbox or behind a corporate proxy that blocks `raw.githubusercontent.com`. When GitHub *is* reachable the CLI refreshes from it and normally caches the result per-user under `<global .winapp>/cache/find-ui`; the built-in corpus is only a floor, never a ceiling. For fallback cache locations, see [Restricted Filesystem Access](#restricted-filesystem-access). Cached data is refreshed at most every 24 hours, or on demand with `--refresh`.

The built-in corpus is re-fetched from GitHub every time a stable release is built, and a refresh that fails **stops the release build** rather than quietly shipping older data — the baker fetches through the same code path `--refresh` uses, so a failure there means the live refresh is broken too and is worth investigating before shipping. A release can still be cut against the previously committed corpus, but only as an explicit override. When results are served from the built-in copy of the Gallery/Toolkit/Reactor corpora, `find-ui` says so on stderr and `--json` output carries `"corpus": "embedded"` (other values: `"network"` for a fresh fetch, `"cache"` for the local cache). A core-only request — `--source core`, or an `--id` set that is all core patterns — reports `"embedded"` too, because the curated core patterns are compiled into the CLI and never fetched; it prints no staleness notice, since `--refresh` cannot change them. The `corpus` field is reported whenever results were served; it is absent only when no corpus could be loaded at all.

Expand Down Expand Up @@ -1824,7 +1824,7 @@ winapp find-api "<query>" [options]
winapp find-api [command] [options]
```

The index is built from the project's restored NuGet/SDK packages (via `project.assets.json`) on first use and refreshed automatically when the project is restored. It lives under the global `.winapp` cache (`cache/find-api/`) and is shared across projects. Restore the project first (`winapp restore` or `dotnet restore`).
The index is built from the project's restored NuGet/SDK packages (via `project.assets.json`) on first use and refreshed automatically when the project is restored. By default, it lives under the global `.winapp` cache (`cache/find-api/`) and is shared across projects. For fallback cache locations, see [Restricted Filesystem Access](#restricted-filesystem-access). Restore the project first (`winapp restore` or `dotnet restore`).

Each match is listed under its namespace with the package that ships it and a one-line summary of what it does, so a result is usable without a second `members` call:

Expand Down Expand Up @@ -2104,7 +2104,120 @@ In **PowerShell** and **pwsh**:
$env:WINAPP_CLI_CACHE_DIRECTORY=d:\temp\.winapp
```

Winapp will create this directory automatically when you run commands like `init` or `restore`.
Winapp creates cache directories when a command needs to save data. SDK packages
use NuGet's package cache; project headers, libraries and bindings use the project's
`.winapp` directory.

### Restricted Filesystem Access

```powershell
winapp find-ui Button
winapp ui list-windows --json
```

These commands can still be useful in an environment that permits access only to
the current directory. When the default global cache is inaccessible, cache-backed
features use `.winapp\cache` directly beneath the current directory and report the
Comment thread
nmetulev marked this conversation as resolved.
fallback. They do not search parent directories or follow filesystem links outside
that directory. An existing readable cache can be used without requiring writes.

| Operation | When global storage is unavailable |
|---|---|
| `find-ui` | Uses local caching, uncached fetched results, or embedded data |
| `find-api` | Builds a local index from accessible project/package metadata; fails if it cannot supply a valid index |
| Tool acquisition and Sandbox runtime payloads | Uses permitted cache storage; required tools and payloads must still pass verification |
| Additional WinUI crash analysis | Can be skipped with a warning without discarding the available dump or ordinary analysis |
| Automatic CLI/template update notices | Skipped when their bookkeeping cannot be saved |
| Read-only UI observation | Can continue without workflow ordering when shared state is inaccessible |
| UI mutations, captures and Sandbox management | Require accessible shared coordination; they never silently run without it |

UI coordination requires a direct local path without junctions or symbolic links,
and existing parent directories must not let other users replace that path.
An untrusted coordination path is rejected even for read-only commands; it is not
treated as an inaccessible cache or a reason to bypass coordination. Correct the
path or its parent permissions before retrying. Ancestor permissions are never
changed automatically.

Store CLI and debugger executables/DLLs reused from the automatic local fallback
must have valid Microsoft signatures. If a local Store tool is rejected, remove
that tool cache and retry to download a verified copy. Unverifiable debugger
layouts are skipped without discarding the available crash diagnostics.

An explicit `WINAPP_CLI_CACHE_DIRECTORY` is authoritative: if that location is
invalid or cannot support the requested operation, fix it or remove the override
rather than expecting an automatic redirect. To select an allowed directory:

```powershell
$env:WINAPP_CLI_CACHE_DIRECTORY = Join-Path (Get-Location) '.winapp\cache'
```

`get-winapp-path --global` still refers to the configured global directory, not to
a command's local fallback.

Storage warnings go to **stderr**, leaving paths and other results on stdout
unchanged. With `--json`, a successful degraded operation writes warning objects
as JSON lines on stderr, for example:

```json
{"warning":{"code":"cache-fallback","message":"Using an accessible local cache."}}
```

A failed command retains its normal error output and nonzero exit code instead of
publishing successful-fallback warnings. `--quiet` suppresses optional storage
warnings. Help, version output and path lookup do not run first-run bookkeeping.

NuGet package storage and configuration are separate dependencies. A default
package-cache fallback does not change package feeds, source mappings, credentials
or signature requirements. Explicit package-cache settings remain authoritative.
If required configuration or package files are inaccessible, supply an allowed
configuration/cache or allow access; winapp does not silently substitute public feeds.
Child `dotnet` commands keep using a readable package cache even when it is read-only.
If a child later needs to write there and fails, choose a permitted `NUGET_PACKAGES`
directory before retrying. winapp does not automatically replay builds or applications
that may already have performed work.

NuGet also requires writable scratch storage for configuration and installation
locks. If that storage is blocked, winapp stops promptly with `NUGET_SCRATCH`
guidance rather than entering NuGet's long lock retry. Use one fully qualified,
permitted scratch directory consistently for every process sharing a package cache.
winapp does not automatically choose a different lock directory for one process.

Filesystem fallback does not grant access to SDKs, certificate stores, Windows package
registration, authentication or the desktop. Commands that require those facilities
still need the corresponding permissions.

### Shared Runtime State

`winapp ui` and commands using `--on sandbox` keep shared state under
`%USERPROFILE%\.winapp\state`:

| Directory | Contents |
|---|---|
| `ui` | Desktop coordination state and locks, separated by Windows sign-in session |
| `targets\<target-key>` | Sandbox ownership, connection and deployment records, bootstrap files, and lifecycle locks |

This location is fixed independently of `WINAPP_CLI_CACHE_DIRECTORY`. Changing the
cache location does not give a process a separate desktop or Sandbox. Packaged and
unpackaged winapp installations use the same state location.

Keep this directory on a writable local drive; network paths are rejected. If winapp
cannot access it, check the directory's permissions and any filesystem links that
redirect it. Do not delete shared state while UI workflows or a managed Sandbox are
running. Save guest work and [end the Sandbox](sandbox-execution.md#removing-an-app-and-ending-the-sandbox)
before removing its state.

Read-only UI commands may continue when this storage is inaccessible, with a warning
that workflow ordering is unavailable. Invalid explicit coordination paths remain
errors. Mutations, screenshots and recordings still require coordination.

AppX layout locks are separate from desktop and Sandbox state. They live beside the
layout they protect, in a reserved `.winapp-layout-locks` directory, so local runs do
not require a global cache merely to lock a build output. Processes targeting the
same layout use the same lock regardless of their cache settings or working directory.
These lock artifacts are excluded from package and deployment payloads. An inaccessible
lock is reported as a storage error, not as another process using the layout.
The lock files can remain after a run; their presence does not mean a process holds
the layout. Exclude `.winapp-layout-locks/` from version control.

### Update Checks

Expand Down
2 changes: 1 addition & 1 deletion plugins/winapp/skills/winapp-find-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ small API. A filter that matches nothing exits `0` and says so explicitly — th
- **Querying a project:** run from (or point `--project-dir` at) a project that has been **restored** — the index is built from `project.assets.json` and the restored NuGet/SDK packages. If the project has never been restored, run `winapp restore` (or `dotnet restore`) first. A solution directory works too: run from the folder holding the `.sln`/`.slnx` and the projects it builds are indexed and answer the query.
- **Querying with no project:** nothing is required. From a directory with no project and no solution, `find-api` answers from the machine-wide **SDK scope** (Windows SDK + Windows App SDK), so an agent can explore the API surface *before* scaffolding an app. No network access is needed in either case.
- The first query builds the index automatically (this can take a few seconds for a large SDK like WindowsAppSDK); subsequent queries are served from the warm cache. The project index refreshes automatically when the project is re-restored.
- No setup is needed beyond a restored project — the index lives under the global `.winapp` cache (`cache/find-api/`) and is shared across projects.
- No setup is needed beyond a restored project — the index normally lives under the global `.winapp` cache (`cache/find-api/`) and is shared across projects. For current-directory-only environments, see [restricted filesystem access](https://github.com/microsoft/WinAppCli/blob/main/docs/usage.md#restricted-filesystem-access).

## Common patterns

Expand Down
2 changes: 2 additions & 0 deletions plugins/winapp/skills/winapp-find-ui/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ winapp find-ui "color picker" --json
every 24 hours, or on demand with `--refresh`); the built-in corpus is a floor,
never a ceiling, so live data always wins. `--source core` searches the curated
built-in patterns and never touches the network at all.
- For blocked global caches and fallback diagnostics, see
[restricted filesystem access](https://github.com/microsoft/WinAppCli/blob/main/docs/usage.md#restricted-filesystem-access).
- **Check the corpus provenance when it matters.** `--json` carries `"corpus"`:
`"network"` (fetched this run), `"cache"` (this machine's earlier fetch), or
`"embedded"` (served from a corpus built into the CLI — a core-only request
Expand Down
4 changes: 4 additions & 0 deletions plugins/winapp/skills/winapp-setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ Use `restore` when you clone a repo that already has `winapp.yaml` but no `.wina

### Private or custom NuGet feeds

If an agent environment restricts filesystem access, see
[restricted filesystem access](https://github.com/microsoft/WinAppCli/blob/main/docs/usage.md#restricted-filesystem-access)
before changing cache settings. Preserve the project's feed and credential configuration.

`init`, `restore`, and `update` download the SDK packages through NuGet, honoring your standard `nuget.config` hierarchy. Private feeds and mirrors, feed credentials (including credential providers), and a custom `globalPackagesFolder` all work as they do for `dotnet restore`. To use only your feed, `<clear />` the inherited sources first:

```xml
Expand Down
5 changes: 5 additions & 0 deletions plugins/winapp/skills/winapp-troubleshoot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ Use this skill when:

## Common errors & solutions

For blocked cache or coordination directories, follow
[restricted filesystem access](https://github.com/microsoft/WinAppCli/blob/main/docs/usage.md#restricted-filesystem-access).
Do not treat a sandbox filesystem restriction as a reason to elevate, disable
coordination, or redirect different desktop-driving processes to separate lock roots.

| Error | Cause | Solution |
|-------|-------|----------|
| "winapp.yaml not found" | Running `restore` or `update` without config | Run `winapp init` first, or `cd` to the directory containing `winapp.yaml` |
Expand Down
4 changes: 4 additions & 0 deletions plugins/winapp/skills/winapp-ui-automation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ therefore makes desktop-driving commands take **cooperative turns** so concurren
steal each other's focus or dismiss each other's menus. That is always on. Read-only commands never
wait.

For read-only observation when shared storage is blocked, and for operations that
must still fail closed, see
[restricted filesystem access](https://github.com/microsoft/WinAppCli/blob/main/docs/usage.md#restricted-filesystem-access).

Keeping the desktop *across* commands is opt-in — without an id, each command is a one-shot that
releases the desktop the moment it finishes:

Expand Down
Loading
Loading