Skip to content

Keep winapp usable when %USERPROFILE%\.winapp is not writable - #941

Open
Nikola Metulev (nmetulev) wants to merge 9 commits into
mainfrom
nmetulev-restricted-winapp-folder-access
Open

Nikola Metulev (nmetulev) wants to merge 9 commits into
mainfrom
nmetulev-restricted-winapp-folder-access

Conversation

@nmetulev

@nmetulev Nikola Metulev (nmetulev) commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

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%\.winapp denied:

Command Before After
Any command Banner, telemetry text and [WARNING] on stdout, every run One-line telemetry notice on stderr; stdout stays clean. The update check is skipped whenever its cache can't be written, including profiles that already ran once (it waited up to 1 s every run)
run Waited 60 s on the layout lock, then said "another winapp process is using the layout" Runs normally without the lock
find-api "Skipped… Access denied" and a stack trace winapp 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 used
store (first install) Fetched release info, then failed while writing Same clear error before any network call
Package installs (init/restore) Created the global folder even though packages go to the NuGet cache Leave it alone
ui commands Every command failed with desktop_coordination_unavailable Runs without taking turns with other winapp ui commands 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/--quiet

Windows Sandbox commands still stop with their existing sandbox_state_unavailable error. Inside Windows Sandbox, winapp uses the guest's own .winapp folder, so this change doesn't affect it.

Deliberately out of scope

  • Coordinating concurrent runs into one layout when the state folder is unwritable. This is rare, and running unlocked is better than refusing every run.
  • A persisted "notice shown" record anywhere else. The one-line stderr notice on each run is enough.
  • Coordinating ui commands through another folder. A private folder could only coordinate with itself, not with winapp ui commands outside the sandbox. Other coordination failures (untrusted or newer-version state) still stop with an error, and so does ui yield.

Validation

  • New tests:
    • short notice on stderr only
    • no root folder created by package installs
    • run lease with a read-only lock folder returns immediately
    • find-api refresh and SDK-index paths give one clear error, and a read-only current index still answers
    • store makes no HTTP request when its folder isn't writable
    • a read-only update cache makes no update request and doesn't repeat the update notice
    • ui inspect and ui invoke run with a denied coordination folder, and inspect prints the warning; both fail without the fix. Other coordination failures and access denied after the command started still return desktop_coordination_unavailable
  • 1065 tests pass across UiCommand, InteractiveDesktop, UpdateNotification, FirstRun and LayoutLease; earlier runs covered RunCommand, WorkspaceSetup, FindApi, ApiMetadata, MSStore, PackageInstallation, MsixServiceIdentity and AzureSignTool. PackagedSandboxMutationLock passed 5 of 5 runs.
  • Manual probe with a deny ACL on the cache, UI and target state folders:
    • --version, find-ui and cert generate succeed with clean stdout.
    • find-api gives the new error.
    • With Notepad open, ui list-windows, ui search and ui screenshot exit 0 with the warning; the screenshot is written. With --json, stdout is pure JSON and stderr is empty.
    • A read-only profile that already has the first-run marker: --version takes about 140 ms and prints nothing extra.
  • scripts\build-cli.ps1 -SkipTests succeeds.

Docs: new "When winapp can't write to the global cache directory" section in docs/usage.md, a note in docs/ui-automation.md and the UI error reference, plus a row in the troubleshoot skill.

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>
Comment thread src/winapp-CLI/WinApp.Cli/Services/ApiSearch/ApiMetadataService.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli.Tests/ApiMetadataServiceTests.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli.Tests/ApiMetadataServiceTests.cs Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Medium severity

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.

Comment thread src/winapp-CLI/WinApp.Cli/Program.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/MSStoreCLIService.cs
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Build Metrics Report

Validation passed. All required build and validation jobs succeeded.

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 57.29 MB 57.30 MB 📈 +4.0 KB (+0.01%)
CLI (x64) 57.34 MB 57.34 MB 📈 +5.5 KB (+0.01%)
MSIX (ARM64) 23.79 MB 23.79 MB 📈 +5.2 KB (+0.02%)
MSIX (x64) 25.26 MB 25.27 MB 📈 +2.5 KB (+0.01%)
NPM Package 49.63 MB 49.64 MB 📈 +6.6 KB (+0.01%)
NuGet Package 49.74 MB 49.74 MB 📈 +3.2 KB (+0.01%)

.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 Time

55ms median (x64, winapp --version) · ✅ -7ms vs. baseline

Try This Build

Installs 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))) 941
Switching between builds often?

Put the tool on your PATH once:

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) -AddToPath

Then this build is just:

winapp-pr 941

Run winapp-pr with no arguments to pick from a list of open PRs.


Updated 2026-09-29 22:53:22 UTC · commit 7dca808 · workflow run

@nmetulev Nikola Metulev (nmetulev) added the agent-preparing Agent is addressing feedback or completing required validation and CI label Sep 29, 2026
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>
Comment thread src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs Fixed
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>
Comment thread src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs
@nmetulev Nikola Metulev (nmetulev) added ready-for-review Agent work and technical checks complete; awaiting review or re-review, not approval or merge and removed agent-preparing Agent is addressing feedback or completing required validation and CI labels Sep 29, 2026
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>
@nmetulev Nikola Metulev (nmetulev) added agent-preparing Agent is addressing feedback or completing required validation and CI and removed ready-for-review Agent work and technical checks complete; awaiting review or re-review, not approval or merge labels Sep 29, 2026
Comment thread src/winapp-CLI/WinApp.Cli.Tests/UiCommandTests.Coordination.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli.Tests/UiCommandTests.Coordination.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli.Tests/UiCommandTests.Coordination.cs Fixed
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@nmetulev Nikola Metulev (nmetulev) added ready-for-review Agent work and technical checks complete; awaiting review or re-review, not approval or merge and removed agent-preparing Agent is addressing feedback or completing required validation and CI labels Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review Agent work and technical checks complete; awaiting review or re-review, not approval or merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants