Skip to content

[Feature]: Add worktree-isolated identity to winapp run #763

Description

Is your feature request related to a problem? Please describe.

Multiple coding agents can build and run the same packaged .NET/WinUI app from different Git worktrees on one Windows machine. The worktrees already have separate build outputs and separate loose-layout AppX directories, but their manifests retain the same package identity. Windows therefore treats them as one package family: running one worktree can unregister or replace another worktree's development deployment, redirect AUMID activation, and share package-scoped state.

The current removal paths also include name-scoped operations that can remove more than the exact deployment selected by the caller. This is unsafe once several managed development deployments may coexist.

Describe the solution you'd like

Add explicit opt-in support:

winapp run <input> --unique-identity

The flag lets agents run the same packaged app concurrently from different worktrees. The caller chooses only whether to opt in; winapp resolves and manages the effective identity automatically.

Goals

  • Allow concurrent loose-layout development deployments of the same packaged .NET/WinUI app from different worktrees.
  • Preserve existing winapp run behavior unless --unique-identity is supplied.
  • Give the same resolved app in the same worktree a stable identity across reruns, regardless of whether it was invoked through ., a .csproj, or solution selection.
  • Keep each worktree's package registration, PFN/AUMIDs, and package-family-scoped app data isolated.
  • Modify only winapp's staged loose-layout copy. Never modify source files or the host build output.
  • Register and unregister only the exact development package owned by winapp.
  • Preserve localized packaged WinUI resources, including resources.pri.

Non-goals for v1

  • Automatic enablement based on detecting a Git worktree. Agents explicitly pass --unique-identity.
  • Unpackaged apps, which have no package identity to vary.
  • Sparse/external-location identity. Sparse identity requires matching <msix> identity embedded into the executable, rewriting the built EXE with mt.exe, invalidating signatures, and creating/signing another identity package.
  • MSIX/MSIXBundle installation, Store packages, package conversion, or production package rebranding.
  • Bundles, resource packages, optional packages, or multi-application package selection.
  • Rewriting public activation contracts such as aliases, protocols, file associations, or COM CLSIDs.
  • Sharing or migrating app data between original and derived identities.
  • Using the derived identity as a public UI-automation selector.

UX and compatibility

  • winapp run without the flag preserves the manifest's original identity.
  • --unique-identity is valid only for packaged folder mode and packaged .NET/WinUI project mode.
  • It is rejected for unpackaged projects, sparse manifests, bundle inputs, and unsupported package shapes before registration.
  • Equivalent invocations that resolve to the same app use the same identity. The user does not choose an identity key.
  • The existing per-worktree loose-layout directory remains the staging directory: by default <resolved build output>\AppX, or the existing --output-appx-directory value.
  • Switching the same staging directory between normal and unique modes first unregisters the exact prior winapp-managed deployment, then rewrites the staged layout. Two registered identities must never reference the same mutable manifest directory.
  • --with-alias is incompatible in v1 because execution aliases are public machine-visible names and duplicate-resolution behavior is not documented reliably.
  • --clean removes data only for the exact effective development package.
  • --unregister-on-exit unregisters only the exact full package name returned by this run.

Deterministic identity algorithm

  1. Resolve the input using the existing run pipeline.
    • Project mode: use the canonical resolved .csproj path selected by project/solution resolution.
    • Folder mode: use the canonical resolved input folder.
  2. Canonicalize the path for Windows identity purposes: absolute path, normalized separators and trailing separator, case-insensitive normalization, and final filesystem target resolution where supported so junction, symlink, short-name, and equivalent path spellings converge.
  3. Read the original manifest identity. The stable family seed is the canonical app path, original Identity/@Name, and exact-case Identity/@Publisher, prefixed by an algorithm version.
  4. Hash the UTF-8 seed with SHA-256 and encode a fixed prefix using a package-name-safe lowercase alphabet.
  5. Set the effective name to <truncated-original>.w<hash>, preserving as much of the original name as possible while staying within the MSIX Identity Name limit of 3-50 characters.
  6. Validate the final package string against Windows rules: only ASCII letters, digits, . and -; no reserved device names or punycode-reserved forms; no trailing dot.
  7. Preserve Publisher, Version, ProcessorArchitecture, ResourceId, and every Application/@Id. Only Identity/@Name changes.
  8. Use Windows package identity APIs such as PackageFamilyNameFromId/PackageFullNameFromId rather than duplicating PFN/full-name algorithms. Each AUMID is <effective PFN>!<Application Id>.
  9. If the derived identity is already associated with a different canonical owner path, fail. Never add a random salt or silently adopt/remove the other deployment.

The algorithm and suffix length must be versioned and covered by golden tests. Once released, changing it is a migration because it changes package family and app-data location.

Transform pipeline

  1. Build/evaluate exactly as winapp run does today. Do not inject identity into source, project properties, or build output.
  2. Copy/synchronize the existing recipe-driven loose layout into the existing per-worktree AppX staging directory.
  3. Acquire a per-layout/per-effective-identity lock so concurrent invocations cannot race manifest transformation, registration, or sidecar updates.
  4. Read and preflight the staged manifest before changing it.
  5. Rewrite only staged Identity/@Name through AppxManifestDocument.
  6. If resources.pri exists, regenerate it under the effective package name while preserving all resource names, qualifiers, values, languages, and paths from the MSBuild-produced PRI. This step is mandatory and fail-closed.
  7. Validate the transformed manifest, effective package identity, PRI root map/languages, executable and assets before registration.
  8. Unregister only the exact prior package recorded for this layout when replacement is required.
  9. Register the staged manifest in DevelopmentMode and capture the resulting exact package full name and install location.
  10. Compute/select the AUMID and launch through the existing path.
  11. Atomically persist ownership metadata only after successful registration. On failure, retain enough prior metadata to clean up safely and report any orphaned exact full name.

Normal fully packaged WinUI/.NET loose-layout apps do not require an embedded <msix> fusion-manifest identity. Embedded identity is a sparse-package concern and is deferred.

PRI/resource fidelity requirement

Changing Identity/@Name changes the default authority for ms-resource and ms-appx resolution. Copying the original identity-bound resources.pri is unsafe, while the current fallback PRI generator indexes only a limited set of image resources and cannot reconstruct a general localized MSBuild/WinUI resource graph.

Before implementation, complete a blocking spike using makepri's PRI indexer:

  1. Build a packaged WinUI sample containing multiple .resw languages, manifest ms-resource: values, XAML resources, qualified images, and ms-appx references.
  2. Stage its MSBuild-produced layout and preserve the original PRI as a separate input.
  3. Generate a new PRI with an explicit /in <effective-package-name> and a config that indexes the existing PRI without auto-splitting resource packages.
  4. Compare detailed dumps and prove that the new root map uses the effective identity and that all languages, candidates, qualifiers, values, and paths survive.
  5. Register and launch two renamed worktree layouts concurrently and exercise localized UI/resource lookup.

If re-indexing the existing PRI cannot preserve full fidelity, the fallback design is an MSBuild-native temporary identity substitution before MakePri runs. The feature must not ship with best-effort PRI generation or a warning-only failure.

Supported and unsupported manifest surfaces

V1 supports package-scoped activation that becomes isolated through the new PFN/AUMID. It does not silently rewrite public contracts.

Preflight all application-level and package-level extensions. Conservatively fail --unique-identity when the manifest declares a surface whose concurrent behavior cannot be proven isolated, including:

  • uap5:AppExecutionAlias
  • packaged COM servers/extensions and CLSIDs
  • protocols and file type associations
  • Windows services
  • startup tasks
  • shell/context-menu integrations
  • any other extension that registers a machine/user-global public name

App services, background tasks, notifications, and similar PFN/AUMID-scoped surfaces may be allowed only after their scoping is verified. Emit a clear warning that external clients hardcoding the original PFN/AUMID will not target the unique deployment.

Errors must name the unsupported manifest element/category and explain that winapp did not rewrite its public contract. V1 has no --allow-conflicts escape hatch.

Ownership, conflicts, reruns, and cleanup

Persist an atomic sidecar adjacent to the staging directory, not inside package content. Suggested fields:

  • schema/algorithm version
  • identity mode
  • canonical owner app path
  • staging layout path
  • original package identity values
  • effective package identity values
  • effective PFN, application ID/AUMID, and exact package full name
  • registered install location
  • transformed manifest hash
  • registration/update timestamps

Safety rules:

  • Treat the sidecar as a hint, not authority. Before removal verify the installed package's exact full name, IsDevelopmentMode, and install location matches the owned staging directory.
  • Use UnregisterByFullNameAsync/RemovePackageAsync(fullName, ...); never remove by name, prefix, family, or an enumeration wider than the selected exact package.
  • Never remove Store/signed/non-development packages.
  • A rerun with the same effective identity and unchanged manifest/layout may use the existing skip-registration optimization.
  • Payload-only updates synchronize the staged files without changing identity.
  • Manifest/identity changes perform exact unregister then register.
  • If another active winapp-managed deployment owns the original or effective identity from a different path, fail with its full name and owner/install path. For an original-identity collision, guide the caller to rerun with --unique-identity; never silently evict the other worktree.
  • If the same layout switches identity mode, remove its exact previously owned deployment before changing the staged manifest.
  • On hash collision, report both canonical paths and fail rather than changing the deterministic identity.

Explicit winapp unregister should consult matching ownership metadata and remove exact owned deployments only. Whether cleanup uses an added unregister --unique-identity flag or transparent sidecar discovery is an implementation UX decision, but name-scoped removal is not acceptable.

JSON additions

Keep the existing top-level AUMID, ProcessId, and Error fields. Add an optional additive Identity object for packaged runs:

{
  "AUMID": "Effective.Name_abcd1234!App",
  "ProcessId": 1234,
  "Identity": {
    "Mode": "Unique",
    "OriginalPackageName": "Original.Name",
    "EffectivePackageName": "Original.Name.w0123456789abcdef",
    "Publisher": "CN=Contoso",
    "PackageFamilyName": "Original.Name.w0123456789abcdef_abcd1234",
    "PackageFullName": "...",
    "ApplicationId": "App",
    "OwnerPath": "C:\\repo-worktree\\App.csproj",
    "LayoutPath": "C:\\repo-worktree\\bin\\Debug\\...\\AppX"
  }
}

Do not expose the internal hash/key as a UI-targeting contract. Human output should identify unique mode and the effective PFN/AUMID, with paths reserved for verbose output and conflict guidance.

Errors and exit behavior

Return nonzero, with equivalent structured --json errors, for:

  • unpackaged, sparse, bundle, multi-app, or otherwise unsupported input
  • unsupported public/global activation surface
  • invalid or over-length derived identity
  • canonicalization failure that prevents a stable key
  • managed identity owned by another path
  • PRI transformation or fidelity validation failure
  • registration state that cannot be proven safe to replace/remove
  • sidecar/installed-package disagreement

Errors should be actionable and include the relevant owner path, install location, exact package full name, manifest category/XPath, and safe remediation when available.

Fidelity and security caveats

  • Package-family-scoped WinRT app data is isolated automatically. Raw Win32 writes to shared %LOCALAPPDATA%, registry locations, files, ports, mutexes, named pipes, databases, and external services are not isolated by package identity.
  • Capability consent, notifications, protocol defaults, and other OS/user state do not migrate from the original identity.
  • Canonical path handling must resist alternate spellings, junctions, symlinks, 8.3 names, and case differences.
  • Sidecar tampering must never authorize broader removal; verify live package metadata before destructive operations.
  • Registration and sidecar writes require locking and atomic replacement for concurrent agents and crash recovery.
  • DevelopmentMode requires a supported local layout filesystem/location. Surface platform errors clearly.
  • Never log secrets from MSBuild properties or package configuration.

Implementation areas

  • RunCommand.cs / RunCommand.ProjectMode.cs: option, validation, threading, UX, JSON, and exact cleanup.
  • MsixService.Identity.cs / skip-registration path: staged transform, ownership-aware rerun, exact replacement.
  • AppxManifestDocument.cs: all-application identity/extension inspection and safe staged identity mutation.
  • PriService.cs / MrtAssetHelper.cs: identity-aware PRI re-indexing, dump validation, and fidelity checks.
  • PackageRegistrationService.cs / IPackageRegistrationService.cs: exact full-name lookup/removal and live ownership verification.
  • UnregisterCommand.cs: eliminate name-scoped removal and integrate managed sidecars.
  • Run result models/source-generated JSON context.
  • npm RunOptions, generated docs/schema, usage docs, plugin skills, and relevant samples.

Test matrix

Unit tests:

  • deterministic derivation and algorithm-version golden vectors
  • equivalent invocation forms resolving to one identity
  • different worktree paths producing different identities
  • case, separators, trailing separators, symlinks/junctions/short paths
  • package-name truncation and every Windows validation boundary
  • manifest rewrite changes only Identity/@Name
  • unsupported extension detection across manifest namespaces
  • sidecar atomicity, stale/corrupt state, lock contention, and crash recovery
  • exact full-name removal only; non-development and wrong-location packages are never removed
  • normal ↔ unique mode switching in the same layout
  • additive JSON success/error shapes

Integration tests on Windows:

  • two worktrees of one packaged .NET/WinUI app register and run concurrently
  • AUMIDs launch the correct physical layouts
  • package-family-scoped app data is distinct and stable across reruns
  • localized .resw, XAML, manifest strings, MRT-qualified assets, and ms-appx references retain fidelity
  • --clean and --unregister-on-exit affect only one worktree
  • default run detects another managed owner and recommends --unique-identity
  • DeveloperMode disabled and unsupported filesystem/location failures
  • x64, x86, and arm64 where CI permits
  • explicit --output-appx-directory
  • unsupported alias/protocol/file-association/COM/service/startup manifests fail before registration

Sample/guide test:

  • clone/copy a packaged WinUI sample into two worktree-like paths, run both with --unique-identity --no-launch --json, assert different effective PFNs and exact registrations, then clean each independently.

Required pre-implementation spikes

  1. Prove lossless identity-aware PRI re-indexing for localized packaged WinUI as described above.
  2. Measure duplicate registration behavior for execution aliases, packaged COM CLSIDs, protocols/file associations, app services, services, and startup tasks; use results to refine the conservative support table.
  3. Validate canonical path resolution across junctions, symlinks, subst drives, 8.3 paths, and worktrees on common developer filesystems.
  4. Verify Windows package identity API use under the CLI's NativeAOT constraints.
  5. Verify exact rerun/removal behavior when a development package is active or files are in use.

Additional context

Current repository behavior already provides most of the needed seams:

  • Folder/project runs stage into separate per-worktree AppX directories, but register the unchanged package identity.
  • MSBuild-generated layouts are copied from .build.appxrecipe, preserving packaged files and existing localized PRI artifacts.
  • AppxManifestDocument already provides namespace-aware identity access.
  • PackageRegistrationService.UnregisterByFullNameAsync already exists, but several run/unregister paths still call broader name-scoped removal.
  • Current fallback PRI generation is intentionally insufficient for this feature because it does not preserve a general localized WinUI resource graph or set an explicit effective index name.
  • Normal packaged loose-layout WinUI apps do not need embedded <msix> identity; sparse apps do and are deferred.

Relevant Windows rules:

  • MSIX Identity/@Name is 3-50 characters and forms the PFN with the publisher ID.
  • AUMID is <package family name>!<Application Id>.
  • DevelopmentMode loose-layout registration does not require package signing, but requires a supported local development layout and cannot be used with bundles.
  • resources.pri top-level resource-map authority typically corresponds to package identity and must be regenerated when the identity name changes.
  • Package removal APIs operate on exact package full names; app-data preservation is valid only for DevelopmentMode packages.

Open implementation decisions that should be resolved by the spikes rather than guessed:

  • exact safe hash encoding/length and canonical filesystem API
  • PRI re-index configuration versus temporary MSBuild identity injection fallback
  • explicit versus sidecar-discovered winapp unregister UX
  • which currently conservative manifest surfaces can be safely supported after measured conflict behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions