{"record":{"id":"fbc9706986298149","repo":"wavetermdev/waveterm","slug":"invalid-term-size-v","errorCode":null,"errorMessage":"invalid term size: %v","messagePattern":"invalid term size: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/jobmanager/jobcmd.go","lineNumber":53,"sourceCode":"\tcleanedUp     bool\n\tptyClosed     bool\n\tprocessExited bool\n\texitCode      *int\n\texitSignal    string\n\texitErr       error\n\texitTs        int64\n}\n\nfunc MakeJobCmd(jobId string, cmdDef CmdDef) (*JobCmd, error) {\n\tjm := &JobCmd{\n\t\tjobId: jobId,\n\t}\n\tif cmdDef.TermSize.Rows == 0 || cmdDef.TermSize.Cols == 0 {\n\t\tcmdDef.TermSize.Rows = 25\n\t\tcmdDef.TermSize.Cols = 80\n\t}\n\tif cmdDef.TermSize.Rows <= 0 || cmdDef.TermSize.Cols <= 0 {\n\t\treturn nil, fmt.Errorf(\"invalid term size: %v\", cmdDef.TermSize)\n\t}\n\tecmd := exec.Command(cmdDef.Cmd, cmdDef.Args...)\n\tif len(cmdDef.Env) > 0 {\n\t\tecmd.Env = make([]string, 0, len(cmdDef.Env))\n\t\tfor key, val := range cmdDef.Env {\n\t\t\tecmd.Env = append(ecmd.Env, fmt.Sprintf(\"%s=%s\", key, val))\n\t\t}\n\t}\n\tcmdPty, err := pty.StartWithSize(ecmd, &pty.Winsize{Rows: uint16(cmdDef.TermSize.Rows), Cols: uint16(cmdDef.TermSize.Cols)})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to start command: %w\", err)\n\t}\n\tunixutil.SetCloseOnExec(int(cmdPty.Fd()))\n\tjm.cmd = ecmd\n\tjm.cmdPty = cmdPty\n\tjm.ptsName = jm.cmdPty.Name()\n\tjm.termSize = cmdDef.TermSize\n\tgo jm.waitForProcess()","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/jobmanager/jobcmd.go#L35-L71","documentation":"MakeJobCmd validates the terminal size before spawning the job's command under a PTY. After substituting the default 25x80 when both are zero, any remaining non-positive Rows or Cols is rejected. This prevents passing an impossible window size to pty.StartWithSize.","triggerScenarios":"Starting a job (StartJob -> MakeJobCmd) with a CommandJobStart TermSize whose Rows or Cols is negative (or one is zero while the other isn't, so the zero-default doesn't apply).","commonSituations":"A client sending a partially-populated TermSize (e.g. Rows set, Cols zero, or negative values from a deserialization bug); a caller constructing CommandJobStartData manually without a valid TermSize.","solutions":["Ensure the caller supplies a positive TermSize (Rows > 0 and Cols > 0) in CommandJobStartData.","If no real size is known, omit it (both 0) so the default 25x80 is applied.","Clamp client-reported sizes before sending the RPC (max(1, size)).","Validate TermSize at the RPC boundary with a guard before calling StartJob."],"exampleFix":"// before\ncmdDef.TermSize = waveobj.TermSize{Rows: -1, Cols: 80}\n// after\ncmdDef.TermSize = waveobj.TermSize{Rows: 25, Cols: 80} // or leave zeroed to get the default","handlingStrategy":"validation","validationCode":"func validTermSize(ts waveobj.TermSize) bool {\n    if ts.Rows == 0 && ts.Cols == 0 {\n        return true // defaults will be applied\n    }\n    return ts.Rows > 0 && ts.Cols > 0\n}","typeGuard":"func isPositiveSize(r, c int) bool { return r > 0 && c > 0 }","tryCatchPattern":"cmd, err := jobmanager.MakeJobCmd(jm, cmdDef)\nif err != nil {\n    if strings.Contains(err.Error(), \"invalid term size\") {\n        cmdDef.TermSize = waveobj.TermSize{Rows: 25, Cols: 80}\n        cmd, err = jobmanager.MakeJobCmd(jm, cmdDef)\n    }\n    if err != nil { return err }\n}","preventionTips":["Validate TermSize at the RPC boundary before calling StartJob","Send both Rows and Cols together or neither","Clamp client-reported sizes to positive values before RPC"],"tags":["validation","terminal","pty"],"backgroundTag":"invalid-term-size","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}