wavetermdev/waveterm · error

cannot start remote job: no router available

Error message

cannot start remote job: no router available

What it means

RemoteStartJobCommand requires a wshrouter.Router to route RPC traffic to the spawned job-manager process. When ServerImpl.Router is nil the server has no RPC routing capability, so starting a remote job is impossible and the call fails fast before spawning any process.

Source

Thrown at pkg/wshrpc/wshremote/wshremote_job.go:136

func (impl *ServerImpl) removeJobManagerConnection(jobId string) {
	impl.Lock.Lock()
	defer impl.Lock.Unlock()
	if _, exists := impl.JobManagerMap[jobId]; exists {
		delete(impl.JobManagerMap, jobId)
		log.Printf("removeJobManagerConnection: removed job manager connection for jobid=%s\n", jobId)
	}
}

func (impl *ServerImpl) getJobManagerConnection(jobId string) *JobManagerConnection {
	impl.Lock.Lock()
	defer impl.Lock.Unlock()
	return impl.JobManagerMap[jobId]
}

func (impl *ServerImpl) RemoteStartJobCommand(ctx context.Context, data wshrpc.CommandRemoteStartJobData) (*wshrpc.CommandStartJobRtnData, error) {
	log.Printf("RemoteStartJobCommand: starting, jobid=%s, clientid=%s\n", data.JobId, data.ClientId)
	if impl.Router == nil {
		return nil, fmt.Errorf("cannot start remote job: no router available")
	}

	wshPath, err := impl.getWshPath()
	if err != nil {
		return nil, err
	}
	log.Printf("RemoteStartJobCommand: wshPath=%s\n", wshPath)

	readyPipeRead, readyPipeWrite, err := os.Pipe()
	if err != nil {
		return nil, fmt.Errorf("cannot create ready pipe: %w", err)
	}
	defer readyPipeRead.Close()
	defer readyPipeWrite.Close()

	cmd := exec.Command(wshPath, "jobmanager", "--jobid", data.JobId, "--clientid", data.ClientId)
	if data.PublicKeyBase64 != "" {
		cmd.Env = append(os.Environ(), "WAVETERM_PUBLICKEY="+data.PublicKeyBase64)

View on GitHub (pinned to a4447c1563)

Solutions

  1. Set ServerImpl.Router to an initialized wshrouter.Router before serving RPCs
  2. Verify the server initialization path that wires Router actually runs before accepting remote:startjob calls
  3. If Router can be nil legitimately, guard the caller with a check and return a clear configuration error

Example fix

// before
impl := &wshremote.ServerImpl{}
impl.RpcClient = client
// after
impl := &wshremote.ServerImpl{}
impl.RpcClient = client
impl.Router = router // wshrouter.Router must be set before serving
Defensive patterns

Strategy: validation

Validate before calling

if server.Router == nil {
    return fmt.Errorf("remote job server not initialized: Router is nil")
}
// then call RemoteStartJobCommand

Type guard

func routerReady(impl *wshremote.ServerImpl) bool {
    return impl != nil && impl.Router != nil
}

Try / catch

rtn, err := server.RemoteStartJobCommand(ctx, data)
if err != nil {
    if strings.Contains(err.Error(), "no router available") {
        // reinitialize server/router wiring before retrying
    }
    return err
}

Prevention

When it happens

Trigger: Calling RemoteStartJobCommand (or the RPC 'remote:startjob' command) on a ServerImpl that was constructed without assigning its Router field, or after the router was never wired during server initialization.

Common situations: Embedding wshremote.ServerImpl in a custom server/daemon and forgetting to set Router; constructing ServerImpl via a zero-value struct literal instead of the normal init path; a router being torn down or not yet initialized when the job start RPC arrives.

Related errors


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