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
- Check job status first (jm.Cmd != nil equivalent via the job status API) and skip StartJob if already running
- Treat this error as success/idempotent: fetch the existing job handle instead of starting a new one
- Use a distinct jobId for each new job instance
- 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
- Make start flows idempotent (query status first)
- Use a unique jobId per job instance
- Debounce duplicate start requests in the UI
- On reconnect, check running state before re-issuing StartJob
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
- job manager only supported on unix systems, not %s
- failed to connect client: %w
- no pending stream (call PrepareConnect first)
- failed to authenticate to server: %w
- JobId mismatch
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/1eae38de87d01141.
Report an issue: GitHub.