Skip to content

[BUG] #23

@kkugot

Description

@kkugot

Bug Description

Never opens a websocket, axios errors out with 400

To Reproduce

Steps to reproduce the behavior:

  1. Run command 'termly-dev start --ai opencode --debug'
  2. See error

Expected Behavior

Expected to register pairing code

Actual Behavior

Errors out with:
❌ Failed to register pairing code: Request failed with status code 400
❌ Unexpected error: Request failed with status code 400

Environment

  • OS: macOS 26.2
  • Node.js version: v25.2.1
  • Termly CLI version: 1.7.0
  • AI tool: Opencode
  • Environment: both Production and Development

Logs

Click to expand logs

Console logs:

[DEBUG] Checking version: @termly-dev/cli-dev@1.7.0
[DEBUG] Version check failed: Request failed with status code 400
[DEBUG] Working directory: /Users/###/###/###
[DEBUG] Project name: Manager
[DEBUG] Manual tool selection: opencode
Using OpenCode v1.1.53
[DEBUG] Pairing code generated: ###
[DEBUG] Public key generated: ###...
ℹ Environment: Development
[DEBUG] Server URL: wss://dev-api.termly.dev
[DEBUG] API URL: https://dev-api.termly.dev
[DEBUG] Registering pairing code: ###
❌ Failed to register pairing code: Request failed with status code 400
❌ Unexpected error: Request failed with status code 400
AxiosError: Request failed with status code 400
    at settle (/Users/###/.nvm/versions/node/v25.2.1/lib/node_modules/@termly-dev/cli-dev/node_modules/axios/dist/node/axios.cjs:2151:12)
    at IncomingMessage.handleStreamEnd (/Users/###/.nvm/versions/node/v25.2.1/lib/node_modules/@termly-dev/cli-dev/node_modules/axios/dist/node/axios.cjs:3545:11)
    at IncomingMessage.emit (node:events:520:35)
    at endReadableNT (node:internal/streams/readable:1701:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:89:21)
    at Axios.request (/Users/###/.nvm/versions/node/v25.2.1/lib/node_modules/@termly-dev/cli-dev/node_modules/axios/dist/node/axios.cjs:4796:41)
    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
    at async registerPairingCode (/Users/###/.nvm/versions/node/v25.2.1/lib/node_modules/@termly-dev/cli-dev/lib/commands/start.js:47:22)
    at async startCommand (/Users/###/.nvm/versions/node/v25.2.1/lib/node_modules/@termly-dev/cli-dev/lib/commands/start.js:129:5)
    at async Command.<anonymous> (/Users/###/.nvm/versions/node/v25.2.1/lib/node_modules/@termly-dev/cli-dev/bin/cli-dev.js:42:5) {
  isAxiosError: true,
  code: 'ERR_BAD_REQUEST',
  config: {
    transitional: {
      silentJSONParsing: true,
      forcedJSONParsing: true,
      clarifyTimeoutError: false,
      legacyInterceptorReqResOrdering: true
    },
    adapter: [ 'xhr', 'http', 'fetch' ],
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    maxBodyLength: -1,
    env: { FormData: [Function], Blob: [class Blob] },
    validateStatus: [Function: validateStatus],
    headers: Object [AxiosHeaders] {
      Accept: 'application/json, text/plain, */*',
      'Content-Type': 'application/json',
      'X-API-Type': 'cli',
      'User-Agent': 'axios/1.13.5',
      'Content-Length': '547',
      'Accept-Encoding': 'gzip, compress, deflate, br'
    },
    method: 'post',
    url: 'https://dev-api.termly.dev/api/pairing',
    data: '{"code":"###","publicKey":"###","projectName":"###","workingDir":"/Users/###/###/###","computerName":"###-MacBook-Pro-2.local","aiTool":"opencode","aiToolVersion":"1.1.53"}',
    allowAbsoluteUrls: true                                                                                     },                                                                                                            request: <ref *1> ClientRequest {                                                                               _events: [Object: null prototype] {                                                                             abort: [Function (anonymous)],                                                                                aborted: [Function (anonymous)],
      connect: [Function (anonymous)],
      error: [Function (anonymous)],
      socket: [Function (anonymous)],
      timeout: [Function (anonymous)],
      finish: [Function: requestOnFinish]
    },
    _eventsCount: 7,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    destroyed: false,
    _last: false,
    chunkedEncoding: false,
    shouldKeepAlive: false,
    maxRequestsOnConnectionReached: false,
    _defaultKeepAlive: true,
    useChunkedEncodingByDefault: true,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    strictContentLength: false,
    _contentLength: 547,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    _closed: false,
    _header: 'POST https://dev-api.termly.dev/api/pairing HTTP/1.1\r\n' +
      'Accept: application/json, text/plain, */*\r\n' +
      'Content-Type: application/json\r\n' +
      'X-API-Type: cli\r\n' +
      'User-Agent: axios/1.13.5\r\n' +
      'Content-Length: 547\r\n' +
      'Accept-Encoding: gzip, compress, deflate, br\r\n' +
      'host: dev-api.termly.dev\r\n' +
      'Connection: keep-alive\r\n' +
      '\r\n',
    _keepAliveTimeout: 0,
    _onPendingData: [Function: nop],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      options: [Object: null prototype],
      defaultPort: 80,
      protocol: 'http:',
      requests: [Object: null prototype] {},
      sockets: [Object: null prototype],
      freeSockets: [Object: null prototype] {},
      keepAliveMsecs: 1000,
      keepAlive: true,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      scheduling: 'lifo',
      maxTotalSockets: Infinity,
      totalSocketCount: 1,
      agentKeepAliveTimeoutBuffer: 1000,
      Symbol(shapeMode): false,
      Symbol(kCapture): false
    },
    socketPath: undefined,
    method: 'POST',
    maxHeaderSize: undefined,
    insecureHTTPParser: undefined,
    joinDuplicateHeaders: undefined,
    path: 'https://dev-api.termly.dev/api/pairing',
    _ended: true,
    res: IncomingMessage {
      _events: [Object],
      _readableState: [ReadableState],
      _maxListeners: undefined,
      socket: [Socket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      rawHeaders: [Array],
      rawTrailers: [],
      joinDuplicateHeaders: undefined,
      aborted: false,
      upgrade: false,
      url: '',
      method: null,
      statusCode: 400,
      statusMessage: 'Invalid header received from client',
      client: [Socket],
      _consuming: true,
      _dumped: false,
      req: [Circular *1],
      _eventsCount: 4,
      responseUrl: 'https://dev-api.termly.dev/api/pairing',
      redirects: [],
      Symbol(shapeMode): true,
      Symbol(kCapture): false,
      Symbol(kHeaders): [Object],
      Symbol(kHeadersCount): 4,
      Symbol(kTrailers): null,
      Symbol(kTrailersCount): 0
    },
    aborted: false,
    timeoutCb: [Function: emitRequestTimeout],
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: false,
    host: 'localhost',
    protocol: 'http:',                                                                                            _redirectable: Writable {                                                                                       _events: [Object],                                                                                            _writableState: [WritableState],                                                                              _maxListeners: undefined,                                                                                     _options: [Object],
      _ended: true,
      _ending: true,
      _redirectCount: 0,
      _redirects: [],
      _requestBodyLength: 547,
      _requestBodyBuffers: [],
      _eventsCount: 3,
      _onNativeResponse: [Function (anonymous)],
      _currentRequest: [Circular *1],
      _currentUrl: 'https://dev-api.termly.dev/api/pairing',
      _timeout: null,
      Symbol(shapeMode): true,
      Symbol(kCapture): false
    },
    Symbol(shapeMode): false,
    Symbol(kCapture): false,
    Symbol(kBytesWritten): 0,
    Symbol(kNeedDrain): false,
    Symbol(corked): 0,
    Symbol(kChunkedBuffer): [],
    Symbol(kChunkedLength): 0,
    Symbol(kSocket): Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'localhost',
      _closeAfterHandlingError: false,
      _events: [Object],
      _readableState: [ReadableState],
      _writableState: [WritableState],
      allowHalfOpen: false,
      _maxListeners: undefined,
      _eventsCount: 8,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      timeout: 0,
      parser: null,
      _httpMessage: [Circular *1],
      autoSelectFamilyAttemptedAddresses: [Array],
      write: [Function: writeAfterFIN],
      Symbol(async_id_symbol): 83,
      Symbol(kHandle): null,
      Symbol(lastWriteQueueSize): 0,
      Symbol(timeout): Timeout {
        _idleTimeout: -1,
        _idlePrev: null,
        _idleNext: null,
        _idleStart: 1020,
        _onTimeout: null,
        _timerArgs: undefined,
        _repeat: null,
        _destroyed: true,
        Symbol(refed): false,
        Symbol(kHasPrimitive): false,
        Symbol(asyncId): 74,
        Symbol(triggerId): 0,
        Symbol(kAsyncContextFrame): undefined
      },
      Symbol(kBuffer): null,
      Symbol(kBufferCb): null,
      Symbol(kBufferGen): null,
      Symbol(shapeMode): true,
      Symbol(kCapture): false,
      Symbol(kSetNoDelay): true,
      Symbol(kSetKeepAlive): true,
      Symbol(kSetKeepAliveInitialDelay): 60,
      Symbol(kBytesRead): 134,
      Symbol(kBytesWritten): 838
    },
    Symbol(kOutHeaders): [Object: null prototype] {
      accept: [Array],
      'content-type': [Array],
      'x-api-type': [Array],
      'user-agent': [Array],
      'content-length': [Array],
      'accept-encoding': [Array],
      host: [Array]
    },
    Symbol(errored): null,
    Symbol(kHighWaterMark): 65536,
    Symbol(kRejectNonStandardBodyWrites): false,
    Symbol(kUniqueHeaders): null
  },
  response: {
    status: 400,
    statusText: 'Invalid header received from client',
    headers: Object [AxiosHeaders] {
      'content-type': 'text/plain',
      connection: 'close'
    },
    config: {
      transitional: [Object],
      adapter: [Array],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 0,
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      maxBodyLength: -1,
      env: [Object],
      validateStatus: [Function: validateStatus],
      headers: [Object [AxiosHeaders]],
      method: 'post',
      url: 'https://dev-api.termly.dev/api/pairing',
      data: '{"code":"###","publicKey":"###","projectName":"###","workingDir":"/Users/###/###/###","computerName":"###-MacBook-Pro-2.local","aiTool":"opencode","aiToolVersion":"1.1.53"}',
      allowAbsoluteUrls: true                                                                                     },                                                                                                            request: <ref *1> ClientRequest {                                                                               _events: [Object: null prototype],                                                                            _eventsCount: 7,                                                                                              _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: false,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      maxRequestsOnConnectionReached: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: true,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      strictContentLength: false,
      _contentLength: 547,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      _closed: false,
      _header: 'POST https://dev-api.termly.dev/api/pairing HTTP/1.1\r\n' +
        'Accept: application/json, text/plain, */*\r\n' +
        'Content-Type: application/json\r\n' +
        'X-API-Type: cli\r\n' +
        'User-Agent: axios/1.13.5\r\n' +
        'Content-Length: 547\r\n' +
        'Accept-Encoding: gzip, compress, deflate, br\r\n' +
        'host: dev-api.termly.dev\r\n' +
        'Connection: keep-alive\r\n' +
        '\r\n',
      _keepAliveTimeout: 0,
      _onPendingData: [Function: nop],
      agent: [Agent],
      socketPath: undefined,
      method: 'POST',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      joinDuplicateHeaders: undefined,
      path: 'https://dev-api.termly.dev/api/pairing',
      _ended: true,
      res: [IncomingMessage],
      aborted: false,
      timeoutCb: [Function: emitRequestTimeout],
      upgradeOrConnect: false,
      parser: null,
      maxHeadersCount: null,
      reusedSocket: false,
      host: 'localhost',
      protocol: 'http:',
      _redirectable: [Writable],
      Symbol(shapeMode): false,
      Symbol(kCapture): false,
      Symbol(kBytesWritten): 0,                                                                                     Symbol(kNeedDrain): false,
      Symbol(corked): 0,                                                                                            Symbol(kChunkedBuffer): [],
      Symbol(kChunkedLength): 0,
      Symbol(kSocket): [Socket],
      Symbol(kOutHeaders): [Object: null prototype],
      Symbol(errored): null,                                                                                        Symbol(kHighWaterMark): 65536,
      Symbol(kRejectNonStandardBodyWrites): false,
      Symbol(kUniqueHeaders): null                                                                                },
    data: 'Invalid header received from client.\n'                                                              },
  status: 400                                                                                                 }

Logs from ~/.termly/logs/cli.log:

[2026-02-09 18:51:30] DEBUG: Checking version: @termly-dev/cli-dev@1.7.0
[2026-02-09 18:51:30] DEBUG: Version check failed: Request failed with status code 400
[2026-02-09 18:51:30] DEBUG: Working directory: /Users/###/###/###
[2026-02-09 18:51:30] DEBUG: Project name: Manager
[2026-02-09 18:51:30] DEBUG: Manual tool selection: opencode
[2026-02-09 18:51:31] DEBUG: Pairing code generated: ###
[2026-02-09 18:51:31] DEBUG: Public key generated: ###...
[2026-02-09 18:51:31] INFO: Environment: Development
[2026-02-09 18:51:31] DEBUG: Server URL: wss://dev-api.termly.dev
[2026-02-09 18:51:31] DEBUG: API URL: https://dev-api.termly.dev
[2026-02-09 18:51:31] DEBUG: Registering pairing code: ###
[2026-02-09 18:51:31] ERROR: Failed to register pairing code: Request failed with status code 400
[2026-02-09 18:51:31] ERROR: Unexpected error: Request failed with status code 400

Additional Context

See logs. Could be a dependency version issue

Possible Solution

If you have ideas about what might be causing this or how to fix it, please share!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions