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
- Set ServerImpl.Router to an initialized wshrouter.Router before serving RPCs
- Verify the server initialization path that wires Router actually runs before accepting remote:startjob calls
- 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
- Always construct ServerImpl through the init path that sets Router
- Add a startup assertion that Router != nil before serving RPCs
- Cover server wiring in an integration test that calls remote:startjob
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
- stream broker route id not available for file copy
- authenticatetokenverify can only be called on root router
- call ${methodName} error: ${respData.error}
- rpc command "${msg.command}" not supported by [${this.routeI
- msg.error
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/03f3af09de2b6454.
Report an issue: GitHub.