SKILL.md
Windows Execution Patterns
The Windows MCP PowerShell tool kills any call at 60 seconds, no exceptions, and leaves hung processes behind. The rule that fixes everything: detach the process and communicate via files, not stdout.
Paths below use <WORK_DIR> for your working directory on the Windows box (e.g. $env:USERPROFILE\work). Set it once and stay inside it.
What times out (all of these)
python script.pywhen the script or interpreter startup runs longStart-Process -WaitandStart-Process -RedirectStandardOutput(both block)Invoke-WebRequestto a slow host with no-TimeoutSecGet-Process | Stop-Processagainst an already-hung processStart-Sleep -Seconds 90
Pattern 1: long-running Python (primary)
- Write the script to a
.pyfile via the filesystem MCP. Never long inline-c "..."strings. - Delete the old output file first, so its presence is a trustworthy done-signal:
$out = "<WORK_DIR>\script_output.json"
if (Test-Path $out) { Remove-Item $out }- Launch detached:
$proc = [System.Diagnostics.Process]::new()
$proc.StartInfo.FileName = "python"
$proc.StartInfo.Arguments = "<WORK_DIR>\my_script.py"
$proc.StartInfo.WindowStyle = "Minimized"
$proc.StartInfo.UseShellExecute = $true # CRITICAL: redirecting stdout forces a blocking wait
$proc.Start() | Out-Null
Write-Host "PID: $($proc.Id)"- The script writes its own results to a file, including errors:
import json
results = {}
try:
# work
results["status"] = "ok"
except Exception as e:
results = {"status": "error", "error": str(e)}
with open(r"<WORK_DIR>\script_output.json", "w") as f:
json.dump(results, f, indent=2)- Poll with separate short calls, each under the 60s window:
Start-Sleep -Seconds 30
if (Test-Path "<WORK_DIR>\script_output.json") { Get-Content "<WORK_DIR>\script_output.json" }
else { Get-Process python -EA SilentlyContinue | Select-Object Id, CPU }Repeat the poll as needed. Keep each sleep at 45s or less.
Pattern 2: quick commands (under 30s expected)
Run directly, but every web request gets -TimeoutSec under 20:
Invoke-RestMethod -Uri "<API_URL>" -Headers @{Authorization = "Bearer <TOKEN>"} -TimeoutSec 15If it might be slow, it is not a Pattern 2 job. Use Pattern 1.
Pattern 3: killing hung processes
Stop-Process can hang on a hung process. taskkill is a direct OS call:
taskkill /F /IM python.exe # by image name
taskkill /F /PID <PID> # by PID
taskkill /F /T /PID <PID> # process tree, last resortPattern 4: network I/O in Python
Any HTTP transfer that could exceed 5 seconds runs as a detached Python script (Pattern 1), stdlib http.client, timeout= always set on the connection. Reachability-check the host first with a 5-second HEAD request from PowerShell before committing to an upload.
Verification
After launching a detached process, poll once. Expect either the output file (read it, check status) or a live PID from Get-Process. Neither present means the launch failed silently: re-check the script path and rerun the launch command reading $proc output. When done, Get-Process python should list nothing you started; leftover PIDs get Pattern 3.
Completion checklist
- [ ] No command issued that can block past 60s
- [ ] Script written to file, not inline
- [ ] Old output file deleted before launch
- [ ] Output file read and status checked
- [ ] No orphan processes left (verified, not assumed)
Any box unchecked: not done. Fix or say so.
Good vs bad
- Bad:
Start-Process python script.py -RedirectStandardOutput out.txt -Wait("I need the output"). Blocks on the pipe, times out at 60s, leaves python.exe orphaned. - Good: detached launch with
UseShellExecute = $true, script writes JSON to<WORK_DIR>, two 30s polls read the file.
Footguns
UseShellExecute = $falseplus stdout redirection recreates the exact blocking behavior this skill exists to avoid. Keep it$trueand use files.- An output file left over from the last run makes a still-running script look finished. Always delete before launch.
Invoke-WebRequestwith no-TimeoutSecblocks on TCP to a dead host and eats the whole 60s window.- Browser-MCP file uploads: CDP
file_uploadmay return{"code":-32000,"message":"Not allowed"}for local paths (browser sandbox, no path fixes it). Workaround: navigate to the localfile:///image, screenshot it to get an in-memory imageId, navigate to the target form, and pass the imageId to the browser MCP's upload_image with the file input's ref.