wavetermdev/waveterm · error

job already started

Error message

job already started

What it means

StartJob enforces that a JobManager can own only one running process; if jm.Cmd is already set, it refuses to start another and returns 'job already started'. This guards against double-spawning the job's command on repeated start requests.

Source

Thrown at pkg/jobmanager/jobmanager.go:228

func (jm *JobManager) SetAttachedClient(msc *MainServerConn) {
	jm.lock.Lock()
	defer jm.lock.Unlock()

	if jm.attachedClient != nil {
		log.Printf("SetAttachedClient: kicking out existing client\n")
		jm.attachedClient.Close()
	}
	jm.attachedClient = msc
}

func (jm *JobManager) StartJob(msc *MainServerConn, data wshrpc.CommandStartJobData) (*wshrpc.CommandStartJobRtnData, error) {
	jm.lock.Lock()
	defer jm.lock.Unlock()

	if jm.Cmd != nil {
		log.Printf("StartJob: job already started")
		return nil, fmt.Errorf("job already started")
	}

	cmdDef := CmdDef{
		Cmd:      data.Cmd,
		Args:     data.Args,
		Env:      data.Env,
		TermSize: data.TermSize,
	}
	log.Printf("StartJob: creating job cmd for jobid=%s", jm.JobId)
	jobCmd, err := MakeJobCmd(jm.JobId, cmdDef)
	if err != nil {
		log.Printf("StartJob: failed to make job cmd: %v", err)
		return nil, fmt.Errorf("failed to start job: %w", err)
	}
	jm.Cmd = jobCmd
	log.Printf("StartJob: job cmd created successfully")

	if data.StreamMeta != nil {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Check job status first (jm.Cmd != nil equivalent via the job status API) and skip StartJob if already running
  2. Treat this error as success/idempotent: fetch the existing job handle instead of starting a new one
  3. Use a distinct jobId for each new job instance
  4. If the old job is defunct, stop/kill it and wait for the process to be reaped before calling StartJob again

Example fix

// before
jm.StartJob(msc, startData) // may error if already started
// after
status := jm.GetJobStatus()
if status.Running {
    return existingHandle, nil // idempotent
}
return jm.StartJob(msc, startData)
Defensive patterns

Strategy: validation

Validate before calling

if jobStatus(jobId).Running {
    return existingJobHandle, nil // already started; skip StartJob
}

Try / catch

rtn, err := jm.StartJob(msc, startData)
if err != nil && err.Error() == "job already started" {
    // idempotent success: reuse existing job handle
    return jm.GetExistingJobHandle(), nil
}
return rtn, err

Prevention

When it happens

Trigger: Calling StartJob twice for the same jobId — e.g. a client retry after a slow first response, two panes starting the same job, or a stale client re-sending start after reconnect while the old process still runs.

Common situations: User clicks 'start job' twice quickly; an automatic reconnect logic re-issues StartJob; orchestration script assumes start is idempotent.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/1eae38de87d01141. Report an issue: GitHub.