Keep winapp usable when %USERPROFILE%\.winapp is not writable - #941
Nikola Metulev (nmetulev) wants to merge 9 commits into
Conversation
Agent sandboxes often allow writes only to the project, so the global .winapp folder is read-only or denied. Handle that simply instead of failing or stalling: - First-run notice: when the marker can't be saved, print a one-line telemetry notice to stderr (stdout stays clean) and skip the update check, which would otherwise wait on every run. - Package installs no longer create the global folder they don't need. - run: if the layout lock file can't be created, proceed without it instead of waiting 60s and reporting a misleading "in use" error. - find-api: check the index cache is writable before indexing and give one clear error pointing at WINAPP_CLI_CACHE_DIRECTORY. A current index in a read-only cache is still used. - store: create the install folder before any download and fail with the same hint. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Existing read-only directories can still trigger update and Store network checks before writability is recognized.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Keeps winapp functional when its global cache is read-only, while providing actionable errors for commands that require cache writes.
Changes:
- Removes unnecessary global-directory creation during package installation.
- Adds read-only-cache handling for first-run notices, layout leases, API indexing, and Store CLI installation.
- Adds regression tests and troubleshooting documentation.
| File | Description |
|---|---|
WorkspaceSetupService.cs |
Stops initializing the global package workspace. |
PackageInstallationService.cs |
Removes global-directory creation. |
MSStoreCLIService.cs |
Adds early install-directory error handling. |
LayoutLease.cs |
Allows unlocked operation when state is unwritable. |
IPackageInstallationService.cs |
Removes obsolete initialization API. |
IFirstRunService.cs |
Introduces detailed notice outcomes. |
FirstRunService.cs |
Emits a concise stderr notice on write failure. |
ApiMetadataService.cs |
Detects unwritable API-index storage. |
Program.cs |
Gates update checks using first-run status. |
PackageInstallationServiceTests.cs |
Verifies the global root remains untouched. |
MSStoreCLIServiceOfflineTests.cs |
Tests early Store installation failure. |
MsixServiceIdentityTests.cs |
Tests unlocked layout-lease fallback. |
FirstRunServiceTests.cs |
Tests stderr-only unsaved notices. |
FakePackageInstallationService.cs |
Updates the test fake interface. |
ConfigurablePackageInstallationService.cs |
Updates the configurable test service. |
AzureSignToolServiceTests.cs |
Updates its package-service fake. |
ApiMetadataServiceTests.cs |
Covers read-only API-index behavior. |
SKILL.md |
Adds cache-permission troubleshooting. |
docs/usage.md |
Documents restricted-cache behavior and recovery. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Build Metrics ReportValidation passed. All required build and validation jobs succeeded. Binary Sizes
.NET Test Results (TRX reports)Other suites are reflected in the overall validation status above. ✅ 7889 passed, 37 skipped out of 7926 tests in 1186.9s (+10 tests, -163.4s vs. baseline) Test Coverage✅ 86.3% line coverage, 80.9% branch coverage · ✅ no change vs. baseline CLI Startup Time55ms median (x64, Try This BuildInstalls the MSIX for your architecture, replacing any previously installed build. Needs the GitHub CLI — the command offers to install it and sign you in if it is missing. & ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) 941Switching between builds often?Put the tool on your PATH once: & ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) -AddToPathThen this build is just: winapp-pr 941Run Updated 2026-09-29 22:53:22 UTC · commit |
Gate the update check inside UpdateNotificationService instead of on the first-run result, so a read-only profile that already has a first-run marker no longer waits for or repeats the check on every run. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Only the first-run placeholder advances LastCheck; a stale cache is rewritten unchanged to confirm it's writable, so a short command that exits mid-refresh still retries next run. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Avoids resetting the process-wide refresh flag on the read-only path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
When the UI coordination folder under %USERPROFILE%\.winapp is access-denied (for example, in an agent sandbox), winapp ui commands now run without taking turns and print one warning instead of failing. Other coordination failures, and access denied after a command has started, still report desktop_coordination_unavailable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…inapp-folder-access
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Replaces #873 with a focused version. When winapp can't write its global folder, as in agent sandboxes that only allow writes to the project, it now works or fails with one clear message. It no longer stalls, prints warnings on every run, or shows stack traces.
What changes for users
With
%USERPROFILE%\.winappdenied:[WARNING]on stdout, every runrunfind-apiwinapp can't write the API index to '<dir>'. Set WINAPP_CLI_CACHE_DIRECTORY to a folder winapp can write to, then retry.An existing, current index is still usedstore(first install)init/restore)uicommandsdesktop_coordination_unavailablewinapp uicommands and prints one warning:⚠ winapp can't access its UI coordination folder, so this command won't wait for other winapp UI commands on this desktop.No warning with--json/--quietWindows Sandbox commands still stop with their existing
sandbox_state_unavailableerror. Inside Windows Sandbox, winapp uses the guest's own.winappfolder, so this change doesn't affect it.Deliberately out of scope
runs into one layout when the state folder is unwritable. This is rare, and running unlocked is better than refusing every run.uicommands through another folder. A private folder could only coordinate with itself, not withwinapp uicommands outside the sandbox. Other coordination failures (untrusted or newer-version state) still stop with an error, and so doesui yield.Validation
runlease with a read-only lock folder returns immediatelyfind-apirefresh and SDK-index paths give one clear error, and a read-only current index still answersstoremakes no HTTP request when its folder isn't writableui inspectandui invokerun with a denied coordination folder, andinspectprints the warning; both fail without the fix. Other coordination failures and access denied after the command started still returndesktop_coordination_unavailablePackagedSandboxMutationLockpassed 5 of 5 runs.--version,find-uiandcert generatesucceed with clean stdout.find-apigives the new error.ui list-windows,ui searchandui screenshotexit 0 with the warning; the screenshot is written. With--json, stdout is pure JSON and stderr is empty.--versiontakes about 140 ms and prints nothing extra.scripts\build-cli.ps1 -SkipTestssucceeds.Docs: new "When winapp can't write to the global cache directory" section in
docs/usage.md, a note indocs/ui-automation.mdand the UI error reference, plus a row in the troubleshoot skill.