jesseduffield/lazygit · error

CreateJobObject

Error message

CreateJobObject: %w

What it means

After the pseudoconsole exists, StartPty creates a Job Object (with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) so closing the job later terminates the whole child process tree — and as a safety net if lazygit itself dies. If CreateJobObject fails, the error is wrapped and the deferred cleanups tear down the pseudoconsole and handles created so far.

Solutions

  1. Run lazygit outside the restrictive host (job-aware CI container, some terminal multiplexers) to confirm the job nesting restriction.
  2. Free handles/memory and retry if the machine is under resource pressure.
  3. Report the environment details upstream if plain cmd/PowerShell also fails, since nesting behavior varies by Windows version.
Defensive patterns

Strategy: fallback

Try / catch

sp, err := oscommands.StartPty(cmd, cols, rows)
if err != nil {
    if strings.Contains(err.Error(), "CreateJobObject") {
        // nested-job restriction (CI/sandbox hosts): run without job-based teardown,
        // but then kill the child process group yourself on exit
        return startPtyWithoutJob(cmd, cols, rows)
    }
    return err
}

Prevention

When it happens

Trigger: windows.CreateJobObject returning an error: the process is already subject to a job that disallows nested job creation, or handle/memory exhaustion at this stage.

Common situations: Running lazygit inside a CI runner, Docker/WSL wrapper, sandbox, or another job-hierarchy that sets JOB_OBJECT_LIMIT_BREAKAWAY_OK off / silent-breakaway-allowed off, blocking new job objects.

Related errors


AI-assisted analysis of jesseduffield/lazygit@c477a2959b (2026-08-15). Data as JSON: /api/errors/6a0ff16a311bb843. Report an issue: GitHub.

Appendix: source

Thrown at pkg/commands/oscommands/pty_windows.go:340

	_ = windows.CloseHandle(inRead)
	_ = windows.CloseHandle(outWrite)
	defer func() {
		if err != nil {
			windows.ClosePseudoConsole(hpc)
			if conhost != 0 {
				_ = windows.CloseHandle(conhost)
			}
		}
	}()

	// The child goes into a job object so that the teardown in Close can
	// terminate the whole process tree. KILL_ON_JOB_CLOSE makes the OS do
	// that when the last handle to the job is closed, which doubles as a
	// safety net: if lazygit exits without running the teardown, the handle
	// is closed for it and the tree is reaped.
	job, err := windows.CreateJobObject(nil, nil)
	if err != nil {
		return StartedPty{}, fmt.Errorf("CreateJobObject: %w", err)
	}
	defer func() {
		if err != nil {
			// Kills the child on error paths where it was already assigned
			// to the job; plain handle cleanup before that.
			_ = windows.CloseHandle(job)
		}
	}()
	limits := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{
		BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{
			LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
		},
	}
	if _, err = windows.SetInformationJobObject(
		job, windows.JobObjectExtendedLimitInformation,
		uintptr(unsafe.Pointer(&limits)), uint32(unsafe.Sizeof(limits)),
	); err != nil {
		return StartedPty{}, fmt.Errorf("SetInformationJobObject: %w", err)

View on GitHub (pinned to c477a2959b)