Skip to content

Commit 96cbb36

Browse files
test: Windows .cmd-shim discovery probe for #149
Add a manual workflow_dispatch probe (win-opencode-probe.yml) + a Windows-gated ignored Rust test that answers, deterministically on windows-latest, whether the dashboard's discovery path can execute a pnpm/npm .cmd shim directly or needs cmd /C. The Rust probe uses the real run_bounded_binary (same tokio Command the code uses) on a temp opencode.cmd and prints raw outcomes; a pwsh step reproduces the real pnpm-global layout and probes the actual shim via CreateProcessW vs cmd /C. CI has full PATH, so this isolates the .cmd-execution mechanism (not the GUI PATH-inheritance angle, which a shell-launched runner can't reproduce). No behavior change; exploratory probe before implementing the fix. Co-authored-by: Alfonso [Magic Context] <288211368+alfonso-magic-context@users.noreply.github.com>
1 parent 9bdc2c4 commit 96cbb36

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Windows opencode discovery probe
2+
3+
# Manual, exploratory probe for #149 (pnpm/npm `.cmd` shim discovery on Windows).
4+
# It answers one question deterministically: can the dashboard's discovery path
5+
# execute a `.cmd` shim, or does it need `cmd /C`? CI runs with a full PATH, so
6+
# it CANNOT reproduce the GUI PATH-inheritance angle — only the .cmd-execution
7+
# mechanism, which is the part we need to confirm before fixing.
8+
on:
9+
workflow_dispatch:
10+
11+
jobs:
12+
probe:
13+
name: Probe .cmd shim execution
14+
runs-on: windows-latest
15+
steps:
16+
- uses: actions/checkout@v5
17+
18+
- name: Install Rust stable
19+
uses: dtolnay/rust-toolchain@stable
20+
21+
# --- Faithful probe: uses the dashboard crate's REAL tokio::process::Command
22+
# path via run_bounded_binary, and prints raw outcomes. ---
23+
- name: Rust probe (real discovery path on a temp .cmd)
24+
working-directory: packages/dashboard/src-tauri
25+
run: cargo test win_cmd_shim_execution_probe -- --ignored --nocapture
26+
27+
# --- Real-world reproduction: a pnpm-global opencode, exactly like #149. ---
28+
- name: Install opencode via pnpm (reproduce #149 layout)
29+
shell: pwsh
30+
continue-on-error: true
31+
run: |
32+
npm install -g pnpm
33+
pnpm add -g opencode-ai
34+
Write-Host "PNPM_HOME=$env:PNPM_HOME"
35+
Write-Host "LOCALAPPDATA=$env:LOCALAPPDATA"
36+
Write-Host "--- pnpm dir contents ---"
37+
Get-ChildItem -Recurse -Filter "opencode*" "$env:LOCALAPPDATA\pnpm" -ErrorAction SilentlyContinue | Select-Object FullName
38+
Write-Host "--- where.exe opencode ---"
39+
where.exe opencode
40+
41+
- name: Probe the real pnpm shim (direct vs cmd /C)
42+
shell: pwsh
43+
continue-on-error: true
44+
run: |
45+
# Resolve the pnpm shim the way discovery would, then try to launch it
46+
# via CreateProcessW (UseShellExecute=$false — identical to Rust's
47+
# Command) vs through cmd /C.
48+
$shim = (where.exe opencode 2>$null | Select-Object -First 1)
49+
if (-not $shim) { Write-Host "no opencode on PATH; skipping"; exit 0 }
50+
Write-Host "resolved shim: $shim"
51+
52+
function Try-Launch($file, $arguments) {
53+
$psi = New-Object System.Diagnostics.ProcessStartInfo
54+
$psi.FileName = $file
55+
$psi.Arguments = $arguments
56+
$psi.UseShellExecute = $false # CreateProcessW path, same as Rust Command
57+
$psi.RedirectStandardOutput = $true
58+
$psi.RedirectStandardError = $true
59+
try {
60+
$p = [System.Diagnostics.Process]::Start($psi)
61+
$out = $p.StandardOutput.ReadToEnd()
62+
$p.WaitForExit()
63+
return "LAUNCHED exit=$($p.ExitCode) out=$($out.Trim())"
64+
} catch {
65+
return "FAILED: $($_.Exception.Message)"
66+
}
67+
}
68+
69+
Write-Host "[direct] $(Try-Launch $shim '--version')"
70+
Write-Host "[cmd /C] $(Try-Launch 'cmd.exe' "/C `"$shim`" --version")"

packages/dashboard/src-tauri/src/commands.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,75 @@ mod tests {
14251425
);
14261426
}
14271427

1428+
// Exploratory probe for #149: can the dashboard's discovery path execute a
1429+
// pnpm/npm `.cmd` shim at all? Rust's Command (like every CreateProcessW
1430+
// caller) does not run batch files directly, so a resolved `opencode.cmd`
1431+
// may fail to launch even when discovery FINDS it. Run on Windows CI with:
1432+
// cargo test --ignored --nocapture win_cmd_shim_execution_probe
1433+
// It prints the raw outcomes; the fix is then asserted by a real regression
1434+
// test once the mechanism is confirmed.
1435+
#[cfg(windows)]
1436+
#[tokio::test]
1437+
#[ignore = "manual Windows discovery probe; run with --ignored --nocapture"]
1438+
async fn win_cmd_shim_execution_probe() {
1439+
let dir = tempfile::tempdir().expect("tempdir");
1440+
let shim = dir.path().join("opencode.cmd");
1441+
std::fs::write(&shim, "@echo off\r\necho probe-opencode 9.9.9\r\n").expect("write shim");
1442+
let shim_str = shim.to_string_lossy().to_string();
1443+
println!("\n[probe] shim at: {shim_str}");
1444+
1445+
// 1. What the REAL discovery path does: run_bounded_binary(<.cmd>, ...).
1446+
// It swallows the launch error, so a None here means "could not run".
1447+
let via_helper = run_bounded_binary(&shim_str, &["--version"]).await;
1448+
println!("[probe] run_bounded_binary(.cmd) -> {via_helper:?}");
1449+
1450+
// 2. Raw Command on the .cmd to expose the actual OS error kind.
1451+
let raw = tokio::process::Command::new(&shim_str)
1452+
.arg("--version")
1453+
.output()
1454+
.await;
1455+
match &raw {
1456+
Ok(o) => println!(
1457+
"[probe] raw Command(.cmd): launched status={} stdout={:?}",
1458+
o.status,
1459+
String::from_utf8_lossy(&o.stdout).trim()
1460+
),
1461+
Err(e) => println!(
1462+
"[probe] raw Command(.cmd): FAILED kind={:?} msg={e}",
1463+
e.kind()
1464+
),
1465+
}
1466+
1467+
// 3. The proposed fix path: cmd /C <.cmd>.
1468+
let via_cmd = tokio::process::Command::new("cmd")
1469+
.args(["/C", &shim_str, "--version"])
1470+
.output()
1471+
.await;
1472+
match &via_cmd {
1473+
Ok(o) => println!(
1474+
"[probe] cmd /C .cmd: launched status={} stdout={:?}",
1475+
o.status,
1476+
String::from_utf8_lossy(&o.stdout).trim()
1477+
),
1478+
Err(e) => println!("[probe] cmd /C .cmd: FAILED {e}"),
1479+
}
1480+
1481+
// 4. where.exe sanity: CI has full PATH, so this isolates the failure to
1482+
// execution rather than discovery (the GUI PATH-inheritance angle of
1483+
// #149 cannot be reproduced in a shell-launched CI runner).
1484+
let where_out = tokio::process::Command::new("where.exe")
1485+
.arg("cmd")
1486+
.output()
1487+
.await;
1488+
println!(
1489+
"[probe] where.exe cmd -> {:?}",
1490+
where_out
1491+
.ok()
1492+
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
1493+
);
1494+
println!("[probe] done\n");
1495+
}
1496+
14281497
#[test]
14291498
fn test_parse_pi_models_output_normal() {
14301499
let input = "provider model context max-out thinking images\nanthropic claude-opus-4-5 200K 64K yes yes \ncerebras gpt-oss-120b 131.1K 32.8K yes no \ngithub-copilot claude-opus-4.7 144K 64K yes yes \n";

0 commit comments

Comments
 (0)