Skip to content

fix(cua-driver): keep Windows autostart on the junction path - #2809

Merged
f-trycua merged 4 commits into
trycua:mainfrom
rsyuzyov:fix/windows-autostart-junction-path
Aug 5, 2026
Merged

fix(cua-driver): keep Windows autostart on the junction path#2809
f-trycua merged 4 commits into
trycua:mainfrom
rsyuzyov:fix/windows-autostart-junction-path

Conversation

@rsyuzyov

@rsyuzyov rsyuzyov commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • stop canonicalising the executable path on Windows when registering autostart
  • retain the invoked bin -> current -> releases/<version> junction path so upgrades can retarget current without rewriting the Scheduled Task
  • keep best-effort canonicalisation on non-Windows platforms
  • preserve extended UNC and long-path forms while simplifying short extended drive-letter paths
  • add focused normalization tests plus a native Windows installer/Scheduled Task regression

Addresses #2137.

Why

The Windows installer exposes cua-driver.exe through two directory junctions:

bin -> packages/current -> packages/releases/<version>

Canonicalising the executable path before registration resolves that chain to a versioned release path. The Scheduled Task is then pinned to the old release when an upgrade retargets current.

On Windows this change stores the executable path returned for the invoked junction path without calling canonicalize. Directly invoking a versioned executable still registers that exact versioned path.

Native Windows result

Windows preserves the invoked path here. A test executable launched through the production-shaped two-junction chain reported the visible bin path from the same current_exe_for_autostart() function used for task registration; it did not report the canonical release path.

The end-to-end Scheduled Task regression then:

  1. installed a real source-built cua-driver.exe behind bin -> current -> probe-v1;
  2. ran autostart enable through bin and verified the task action retained that visible path;
  3. retargeted current to a separately built probe-v2 executable without modifying the task;
  4. started the task and verified probe-v2 executed and reported its version marker.

This proves both the Windows current_exe() premise and the actual Task Scheduler upgrade behavior. The next-version fixture is synthetic so the regression is deterministic; the same run separately passed the released installer/reinstall migration smoke and the source-built local install/daemon smoke.

Validation

Final head: 0cc94db92bab723e929a8428c486a17ab76f339a
Base: 32f0ef8537c503918116ab7d4cc4c1c262c50870

  • E2E: Rust Windows interactive run 30969836896, exact requested source SHA 0cc94db92bab723e929a8428c486a17ab76f339a: passed
    • native autostart unit slice: 9 passed, including the two-junction current_exe_for_autostart() case
    • real Scheduled Task junction-retarget regression: passed
    • released install and legacy migration: passed
    • isolated local install and daemon get_config: passed; source_sha was 0cc94db92bab723e929a8428c486a17ab76f339a, version 0.17.0
  • rust-windows-install-local artifact 8916283387:
    • config SHA-256 d22e3bd98c6ec45fc7d3077301b30cb628c602f941e9291ead0c63f6c270f406
    • version SHA-256 4bd6213fdbfebac8985b2e541b0dd0bc52a823c9b71a4db88dcae2e9db4942e4
    • release install SHA-256 05c732d9cceacfaac027a2c7dce72b99354f70a1cb9419e9aeae306611cfd58c
    • reinstall SHA-256 ba6f86b65206c63124369c928b024b84044f3b3734ca4ce877d8a0f6173897fb
  • local focused autostart slice on macOS: 7 passed; Windows-only cases were certified in the exact-head run above

Contributor attribution is preserved across the rebase: Roman Syuzyov remains author of all three contributed commits.

Copilot AI review requested due to automatic review settings August 3, 2026 08:38
@rsyuzyov
rsyuzyov requested a review from f-trycua as a code owner August 3, 2026 08:38

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.

Pull request overview

Updates Windows autostart registration so the Scheduled Task stores the junction path to cua-driver.exe (rather than a canonicalized, version-pinned release path), ensuring upgrades that flip current are picked up automatically. This aligns the implementation with the intended Windows upgrade behavior described in #2137.

Changes:

  • On Windows, stop canonicalizing std::env::current_exe() when computing the autostart executable path (preserves the junction chain).
  • Keep canonicalization on non-Windows platforms to resolve symlink chains.
  • Expand/update documentation around why Windows must not canonicalize this path.
Suppressed comments (1)

libs/cua-driver/rust/crates/cua-driver/src/autostart.rs:163

  • On Windows, stripping the \\?\ extended-length prefix unconditionally can change semantics for UNC paths (\\?\UNC\server\share\...) and can break long paths where the prefix is required to exceed MAX_PATH. Since the comment says this is for readability, it should only strip in the safe \\?\C:\... case (and ideally only when the unprefixed path is short enough).
    #[cfg(target_os = "windows")]
    let path = path
        .strip_prefix(r"\\?\")
        .map(str::to_owned)
        .unwrap_or(path);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread libs/cua-driver/rust/crates/cua-driver/src/autostart.rs Outdated
@rsyuzyov rsyuzyov changed the title fix(cua-driver)(windows): keep the autostart path on the junction so upgrades are picked up fix(cua-driver): keep Windows autostart on the junction path Aug 3, 2026
@f-trycua
f-trycua force-pushed the fix/windows-autostart-junction-path branch 4 times, most recently from abb4328 to 5a2ba38 Compare August 5, 2026 02:26
rsyuzyov and others added 4 commits August 4, 2026 21:36
`current_exe_for_autostart` canonicalised the executable path before baking it
into the Scheduled Task. On Windows that resolves the
`bin -> current -> releases/<version>` junction chain down to a versioned
release path, so flipping `current` during an upgrade has no effect: the task
keeps launching the previous build — the opposite of what the doc comment
promises.

Since the 0.15 action-result contract change this is no longer a silent version
skew. On a machine upgraded to 0.17.0 while the task still pointed at the
0.14.1 release, every call fails:

    Cua Driver daemon on \.\pipe\cua-driver is incompatible: invalid daemon
    response: incompatible daemon: contract version 0.2.0 does not match SDK 0.6.0

Re-running `autostart enable` does not repair it for a normal user: the task is
registered with RunLevel=HighestAvailable, so from a non-elevated shell the
rewrite silently does nothing while reporting success.

Windows now stores the path as invoked; other platforms keep canonicalize for
symlink resolution.

Verified with `cargo check -p cua-driver` on the pinned 1.97.1 toolchain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…aths

Two review nits on the surrounding code:

- the doc comment claimed non-Windows paths are canonicalised, while the
  implementation falls back to the non-canonical path on error — say
  "best-effort" so the doc matches the code
- the `\?\` strip was unconditional, which mangles the UNC namespace
  (`\?\UNC\server\share` -> `UNC\server\share`) and would break paths that
  need the prefix to exceed MAX_PATH. Restrict it to `\?\<drive>:\...`
  under 260 chars.
`cargo fmt --all -- --check` (the pinned 1.97.1 toolchain the CI job uses)
reflowed the inline byte checks. Pull them into a small named helper instead,
which reads better than the wrapped chain.
@f-trycua
f-trycua force-pushed the fix/windows-autostart-junction-path branch from 5a2ba38 to 0cc94db Compare August 5, 2026 02:36

@f-trycua f-trycua left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed final two-file diff at 0cc94db. Exact Windows installer/Scheduled Task run 30969836896 passed the invoked-junction premise, real current-retarget launch, released migration, and source-provenance smoke; all repository checks pass and contributor attribution is preserved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants