Dev Investigation Notes
When a hidden-VBS Python script in Startup occasionally pops an error dialog and silently fails to launch only at boot
This is a translated article. Read the original in Korean ↗
I was building a local voice assistant (voice-assistant) for Windows that stays running and answers when it hears a wake word. To get it to start without a window, I put a hidden VBS in the Startup folder that launched Python (pythonw). Every so often a VBS error dialog popped up, and on some days the assistant didn't start at all after boot. Nothing showed up in the logs.
Conclusion
On Error Resume Next hides errors . The cause of the repeated wscript failures is also still unconfirmed . Keep the delay and retry settings .
Run pythonw.exe directly instead of going through a batch file or VBS. The Hidden setting hides the task from the task list. It does not hide a cmd window. pythonw doesn't create a console in the first place.
For a voice assistant, choose your own account and ‘사용자가 로그온할 때만 실행’ ("Run only when user is logged on") (Interactive). If you pick ‘로그온 여부와 관계없이’ ("Run whether user is logged on or not") to avoid the window, the task also runs in a different interactive session (logon type).
Below is an unverified example of how to apply this. Change the paths and module, and move any environment setup and file logging from the batch file into the app.
$user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$action = New-ScheduledTaskAction -Execute "C:\myapp\.venv\Scripts\pythonw.exe" -Argument "-m src.main" -WorkingDirectory "C:\myapp"
$principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $user
$trigger.Delay = "PT45S"
$settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -MultipleInstances IgnoreNew
Register-ScheduledTask -TaskName "MyAssistant" -Action $action -Trigger $trigger -Settings $settings -Principal $principal
# Single-instance guard example - a second process that starts up exits on its own
import ctypes, sys
ERROR_ALREADY_EXISTS = 183
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
_mutex = kernel32.CreateMutexW(None, False, "Global\\MyAssistantSingleton") # keep the reference alive until the process exits
if ctypes.get_last_error() == ERROR_ALREADY_EXISTS:
print("이미 실행 중인 인스턴스가 있어 종료함")
sys.exit(0)
(The printed message means "An instance is already running, exiting.")
Situation
The whole voice assistant runs on the local PC: wake-word detection, then STT (speech-to-text), then an LLM call (Claude Code CLI), then speech synthesis (TTS). Responses come from running claude -p as a child process . Python stays resident as the venv's pythonw.exe, the Python build that has no console window .
The auto-start chain looked like this:
시작 프로그램 폴더의 VoiceAssistant.lnk
-> wscript.exe start_assistant_hidden.vbs (WshShell.Run, 창 스타일 0 = 숨김)
-> 배치 파일
-> pythonw.exe
(In English: VoiceAssistant.lnk in the Startup folder → wscript.exe start_assistant_hidden.vbs (WshShell.Run, window style 0 = hidden) → batch file → pythonw.exe.)
The input device is a USB audio interface, and on startup the app looks the device up by name .
Symptom
- An occasional VBS error dialog. An error alert about "hidden_vbs 무슨 파일" ("some hidden_vbs file") popped up now and then. The error text was never recorded .
- No assistant after boot. The shortcut was still in the Startup folder, but not a single
pythonw.exewas running . It hadn't been disabled in Task Manager either (no disabled entry in the registry) . - The log was silent. The PC booted at 22:33, and after that
main_log.txtdidn't gain a single line. It looked like the app never started, not like it started and then crashed . - And yet it worked fine when run by hand. Running it directly with
python.exegot all the way to "준비 완료" ("ready"), and running the batch file directly also brought it up .
Cause
Not confirmed. Here's what was confirmed, kept separate from what's only a guess.
Confirmed:
- The VBS error dialog appears because Windows Script Host shows a modal dialog when it hits a script error, even when the script is launched hidden. With no error text on record, the specific error couldn't be identified .
- The boot failure only reproduced on the path that ran the VBS through
wscript. It failed four times in a row (0pythonwprocesses, 0 bytes added to the log) , and the very next run on the same path succeeded . A 0-byte log on failure means not a single line of the Python code ran . - Running it with
cscript(the console script host) also exited with exit 0 and no error, andpythonwstill didn't start . But nothing in the record says theOn Error Resume Nextat the top of the VBS was removed for that run. If that line was still there,cscriptwould also skip over errors without printing them, so this result alone can't show that "no error was being swallowed." - Switching the window style to 1 (visible) made it launch, and switching back to 0 (hidden) made it launch too, that time. So hidden mode couldn't be called the cause either .
Guesses (unverified):
- The failures seemed to line up with the period right after boot when Defender real-time scanning and the startup-item scan are running and the USB audio device may not have been enumerated yet . The app did have code that dies with a
RuntimeErrorif it can't find the device by name . But since the log was 0 bytes on failure, it may never have reached the device step at all, so this guess doesn't quite fit.
A caveat: earlier the same day, in the early morning, while investigating the same symptom after an earlier boot, the shell used for the investigation cleaned up the child processes it had launched once each call finished. That mixed fake "starts, then disappears" failures into the results . When the app was launched outside that shell (via WMI), it stayed up just fine . The "four failures in a row" above were also run from the same kind of tool shell, so it can't be fully ruled out that the same effect got mixed in. If you're testing reproduction from an automation tool or a remote shell, first rule out that the shell itself is killing your processes.
Fix
Without knowing the cause, I moved the launch to a more robust setup.
Before: hiding the symptom. I put On Error Resume Next at the top of both VBS files to stop the error dialog . The dialog went away, but the cause stayed unknown. A few days later, chasing the boot failure, the first thing I had to suspect was "is this swallowing the error?" , and as long as that line stayed in, I couldn't see errors even by running it with cscript.
After: replacing how it's launched
- Registered a Task Scheduler task,
VoiceAssistantJarvis: runs 45 seconds after logon, retries up to 3 times at 1-minute intervals on failure, ignores a new run if one is already running. It bypasses the silently failingwscriptchain and runs the batch file directly. Instead of deleting the Startup folder shortcut, I moved it toscripts/_disabled_startup/to prevent a double launch . - Made the app hold up better at boot. On startup it prints
[main] === 시작 <타임스탬프> (pid ...) ===(시작 <타임스탬프>= "start <timestamp>"). If it fails to come up again, the log alone tells you whether it even attempted to start. The input stream retries for up to 60 seconds until the device is ready . Launched from the scheduler, this startup log line actually got written . - Blocked a second instance with a mutex. When I deliberately launched one more, it printed "이미 실행 중인 인스턴스가 있어 종료함" ("An instance is already running, exiting") and quit immediately .
What was verified: running the task on demand confirmed startup, logging, and the mutex. There is no record of confirming it with an actual reboot .
Bonus — two pythonw processes running is not a duplicate launch
Task Manager showed two pythonw.exe per app, which made me suspect a duplicate launch. Checking the parent PIDs and CPU showed the parent was the venv's launcher stub (CPU 0), and one child process was doing the actual work (CPU 79.9%) . To shut it down, filter by command line (CommandLine) rather than by a single PID, and kill both . To prevent real duplicates, use the mutex shown above.
Reporter's Notes — once the error dialog was off, there was nothing to see
pythonw running main_log.txt hasn't gained a single line since. It never started On Error Resume Next at the top to stop the VBS error dialog. That tells it to ignore errors and move on to the next line. Isn't that swallowing the real error? cscript instead of wscript. Same script host, but it prints errors to the console instead of a dialog.cscript: no error, exit 0. But pythonw still doesn't start On Error Resume Next isn't the culprit.cscript too. Exit 0 could mean "there was no error" or "there was an error and we couldn't see it."cscript showed you nothing.wscript several times, same as at boot, and count the success rate.wscript path is the culprit.wscript. The log got all the way to "준비 완료" ("ready") wscript chain and run the batch file directly. Run 45 seconds after logon, retry up to three times at 1-minute intervals on failure, ignore a new run if it's already up [main] === 시작 ... (pid 18320) === ("start") On Error Resume Next is a switch that turns off the error dialog, not a tool that fixes the cause. If you can't pin down the cause, don't hide the errors. Move to a launch setup that leaves a trace when it fails and gets itself back up.