fix(scan): force-kill stale AE processes before each launch (fresh start)
Build backend images / build content-svc (push) Failing after 54s
Build backend images / build file-svc (push) Failing after 56s
Build backend images / build gateway (push) Failing after 57s
Build backend images / build identity-svc (push) Failing after 58s
Build backend images / build notification-svc (push) Failing after 1m4s
Build backend images / build render-svc (push) Failing after 2m27s
Build backend images / build studio-svc (push) Failing after 55s

PrepareFreshAE = taskkill AfterFX/aerender/AfterFXLib/dynamiclinkmanager/QT32
+ 2s settle + clear crash markers, then launch. A hung/zombie AE from a prior
job would otherwise block or corrupt the new run. RunScan now calls it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 21:27:34 +03:30
parent 6e5efbdb2c
commit f0ce286527
2 changed files with 37 additions and 16 deletions
+36 -15
View File
@@ -4,27 +4,50 @@ import (
"os"
"os/exec"
"path/filepath"
"time"
)
// aeProcesses are the AE-related executables to force-kill before a fresh launch.
var aeProcesses = []string{
"AfterFX.exe", // the AE app
"aerender.exe", // the headless renderer
"AfterFXLib.exe", // AE render engine
"dynamiclinkmanager.exe", // Dynamic Link
"Adobe QT32 Server.exe", // QuickTime/media server
}
// PrepareFreshAE guarantees a clean AE start before a scan/render: it force-kills
// any leftover AE process (a hung/zombie instance from a prior job would otherwise
// block or corrupt the new launch), waits for them to release file locks, then
// clears the crash/Safe-Mode markers. Call this right before launching afterfx/aerender.
func PrepareFreshAE() {
if os.Getenv("APPDATA") == "" {
return // non-Windows / dev
}
KillAEProcesses()
time.Sleep(2 * time.Second) // let the OS reap processes + release locks
ClearAECrashState()
}
// KillAEProcesses force-terminates every AE-related process tree (taskkill is a
// Windows built-in — no external dep). Errors (e.g. "process not found") are ignored.
func KillAEProcesses() {
for _, name := range aeProcesses {
_ = exec.Command("taskkill", "/F", "/T", "/IM", name).Run()
}
}
// ClearAECrashState removes the markers After Effects uses to decide it crashed,
// so the blocking "Crash Repair Options" (Safe Mode) dialog never appears on a
// headless launch. Two parts:
//
// so the blocking "Crash Repair Options" (Safe Mode) dialog never appears:
// 1. SCRPriorState.json in each AE prefs version dir (session crash-recovery state).
// 2. HKCU\Software\Adobe\After Effects\AppStates — AE writes a per-session GUID
// subkey on startup and removes it on a clean exit; a leftover one (after a
// kill/crash) triggers Safe Mode. reg.exe is a Windows built-in (no external
// dep / cgo), so we shell out to it.
//
// Targeted (vs. wiping all prefs) so the node keeps its AE preferences. Safe no-op
// on non-Windows (APPDATA unset).
// 2. HKCU\Software\Adobe\After Effects\AppStates — a leftover per-session GUID
// (after a kill/crash) trips Safe Mode. reg.exe is a Windows built-in.
// Targeted (vs. wiping all prefs) so the node keeps its AE preferences.
func ClearAECrashState() {
appData := os.Getenv("APPDATA")
if appData == "" {
return // non-Windows / dev
return
}
// 1. session crash-recovery files
base := filepath.Join(appData, "Adobe", "After Effects")
if entries, err := os.ReadDir(base); err == nil {
for _, e := range entries {
@@ -33,7 +56,5 @@ func ClearAECrashState() {
}
}
}
// 2. registry session/crash state
_ = exec.Command("reg", "delete", `HKCU\Software\Adobe\After Effects\AppStates`, "/f").Run()
}
+1 -1
View File
@@ -33,7 +33,7 @@ func WriteScanScript(workDir string) (string, error) {
// afterfx -r runs the script and the script calls app.quit(); we still poll for the
// output file because afterfx can return before the file is flushed.
func RunScan(ctx context.Context, afterfxPath, aepPath, workDir, outPath, mode string) ([]byte, error) {
ClearAECrashState() // avoid the "Crash Repair Options" dialog hanging a headless launch
PrepareFreshAE() // kill any stale AE + clear crash/Safe-Mode markers → guaranteed fresh launch
scriptPath, err := WriteScanScript(workDir)
if err != nil {
return nil, fmt.Errorf("write scan script: %w", err)