{"record":{"id":"6ff76a37304dce96","repo":"sipeed/picoclaw","slug":"assign-process-to-job-object-w","errorCode":null,"errorMessage":"assign process to job object: %w","messagePattern":"assign process to job object: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/isolation/platform_windows.go","lineNumber":114,"sourceCode":"\t\twindows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE|windows.PROCESS_QUERY_LIMITED_INFORMATION|windows.SYNCHRONIZE,\n\t\tfalse,\n\t\tuint32(cmd.Process.Pid),\n\t)\n\tif err != nil {\n\t\t_ = windows.CloseHandle(job)\n\t\tif resources.token != 0 {\n\t\t\t_ = resources.token.Close()\n\t\t}\n\t\treturn fmt.Errorf(\"open process for job assignment: %w\", err)\n\t}\n\n\tif err = windows.AssignProcessToJobObject(job, proc); err != nil {\n\t\t_ = windows.CloseHandle(proc)\n\t\t_ = windows.CloseHandle(job)\n\t\tif resources.token != 0 {\n\t\t\t_ = resources.token.Close()\n\t\t}\n\t\treturn fmt.Errorf(\"assign process to job object: %w\", err)\n\t}\n\n\tif resources.token != 0 {\n\t\t_ = resources.token.Close()\n\t}\n\tresources.job = job\n\twindowsProcessResourcesByPID.Store(cmd.Process.Pid, resources)\n\tgo reapWindowsProcessResources(cmd.Process.Pid, proc, job)\n\treturn nil\n}\n\nfunc cleanupPendingPlatformResources(cmd *exec.Cmd) {\n\tif cmd == nil {\n\t\treturn\n\t}\n\tresourcesAny, ok := windowsPendingResources.LoadAndDelete(cmd)\n\tif !ok {\n\t\treturn","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/isolation/platform_windows.go#L96-L132","documentation":"Thrown by the Windows isolation backend's post-start hook. After a child process is spawned with a restricted token, the runtime creates a job object (JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE), opens the child PID with PROCESS_SET_QUOTA|PROCESS_TERMINATE|PROCESS_QUERY_LIMITED_INFORMATION|SYNCHRONIZE, and calls AssignProcessToJobObject. If that Win32 call fails, isolation setup aborts, the job/process/token handles are closed, and this wrapped error (including the syscall.Errno) is returned.","triggerScenarios":"AssignProcessToJobObject failing: (1) the child exited between OpenProcess and assignment (fast-failing command, bad executable path); (2) the child is already in another job that does not allow breakaway/nesting — nested jobs require Windows 8+, so a Windows 7 parent already in a job fails with ERROR_NOT_SUPPORTED / ERROR_ACCESS_DENIED; (3) the parent itself runs inside a restrictive job or sandbox (CI runner, Docker Desktop, EDR) that denies PROCESS_SET_QUOTA-style operations; (4) security software blocking handle operations on the child.","commonSituations":"Running picoclaw's isolation mode inside CI agents or container sandables that already confine processes to jobs; launching very short-lived children (the process is gone before it can be attached); Windows 7/Server 2008 R2 hosts where a child can belong to only one job; antivirus interference with job assignment.","solutions":["Inspect the wrapped errno (errors.As to syscall.Errno): ERROR_ACCESS_DENIED (5) points to job/permission confinement, ERROR_NOT_SUPPORTED (50) to nested-job limits on Windows 7","Verify the child is still alive right after Start (check cmd.ProcessState / wait later); if it exits instantly, fix the command or path being launched","Run the parent process outside already-restrictive job objects (avoid nesting the runtime inside CI/container jobs), or upgrade the host to Windows 8+ for nested job support","If confinement is unavoidable, run with isolation disabled on that host (isolation.enabled=false) since the job-object guarantee cannot be established","Reproduce manually with a minimal Go snippet calling AssignProcessToJobObject to confirm the host policy blocks it"],"exampleFix":"# before: launching an instantly-exiting command under isolation\nisolation: {enabled: true}\ncommand: [\"nonexistent-tool\"]  # child dies before job assignment\n\n# after: verify the child survives, launch a real command\nisolation: {enabled: true}\ncommand: [\"C:\\\\Tools\\\\tool.exe\"]","handlingStrategy":"try-catch","validationCode":"// before spawning isolated children on windows, detect a confining parent job\nvar inJob bool\nif err := windows.IsProcessInJob(windows.CurrentProcess(), 0, &inJob); err == nil && inJob {\n    // parent is already job-confined; nested assignment can fail — plan for it or refuse isolation here\n    log.Warn(\"parent process is inside a job object; isolation assignment may fail\")\n}","typeGuard":null,"tryCatchPattern":"if err := launchIsolated(cmd); err != nil {\n    if strings.Contains(err.Error(), \"assign process to job object\") {\n        var errno syscall.Errno\n        if errors.As(err, &errno) {\n            switch errno {\n            case windows.ERROR_ACCESS_DENIED, windows.ERROR_NOT_SUPPORTED:\n                // host policy prevents job confinement: kill the unrestricted child\n                _ = cmd.Process.Kill()\n                return fmt.Errorf(\"isolation unavailable on this host (errno %d); refusing to run unconfined\", errno)\n            }\n        }\n    }\n    return err\n}","preventionTips":["Run the parent outside CI/container job sandboxes, or on Windows 8+ where nested jobs are supported","Only enable isolation for children that outlive the post-start hook — avoid instantly-exiting commands","Log the unwrapped errno on this path so host-policy denials are diagnosable"],"tags":["windows","isolation","job-object","win32","subprocess"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}