hcengineering/platform · error

err.message

Error message

err.message

What it means

The export service's global Express error middleware. Errors that are instances of ApiError are returned with their own status code and { code, message }; any other thrown error is reported to Analytics, logged via measureCtx.warn, and returned as 500 with { message: err.message } — or the raw error object if the message is empty. It converts uncaught handler exceptions into JSON error responses.

Source

Thrown at services/export/pod-export/src/server.ts:648

          await sourceClient.close()
          await targetClient.close()
        }
      } catch (err: any) {
        measureCtx.error('Export to workspace request failed:', err)
        const errorMessage = err instanceof ApiError ? err.message : 'Export to workspace request failed'
        res.status(err instanceof ApiError ? err.code : 500).send({ message: errorMessage })
      }
    })
  )

  app.use((err: any, _req: any, res: any, _next: any) => {
    measureCtx.warn(err)
    if (err instanceof ApiError) {
      res.status(err.code).send({ code: err.code, message: err.message })
      return
    }

    res.status(500).send(err.message?.length > 0 ? { message: err.message } : err)
  })

  return {
    app,
    close: () => {
      void storageAdapter.close()
    }
  }
}

async function createPlatformClient (token: string): Promise<Client> {
  setMetadata(client.metadata.ClientSocketFactory, (url) => {
    return new WebSocket(url, {
      headers: {
        'User-Agent': process.env.SERVICE_ID
      }
    }) as never as ClientSocket
  })

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Inspect the response body: if it has { code, message } it is an ApiError — act on that code; otherwise check server logs (measureCtx.warn) for the full stack.
  2. Ensure the request token is well-formed and current (decodeToken throws on garbage), and re-login if expired.
  3. Check storage adapter configuration/health if the 500 follows an upload/download path.
  4. If the response is the raw error object (no message), check Analytics for the serialized error to find the root cause.

Example fix

// before (empty-message error returned as raw object)
throw new Error('')
// after
throw new ApiError(400, 'Missing required parameters')
Defensive patterns

Strategy: type-guard

Validate before calling

// client: check token shape before calling export APIs
if (typeof token !== 'string' || token.split('.').length !== 3) {
  throw new Error('malformed token — obtain a fresh one from login')
}

Type guard

function isApiErrorBody(body: unknown): body is { code: number; message: string } {
  return typeof body === 'object' && body !== null &&
    typeof (body as any).code === 'number' && typeof (body as any).message === 'string'
}

Try / catch

const res = await exportApi.call(endpoint, payload)
if (!res.ok) {
  const body = await res.json().catch(() => null)
  if (isApiErrorBody(body)) throw new ApiError(body.code, body.message) // server-side ApiError
  throw new Error(body?.message ?? `export service 500: ${res.status}`)
}

Prevention

When it happens

Trigger: Any export HTTP request whose handler throws a non-ApiError: decodeToken failures on malformed tokens, storage adapter errors, unexpected exceptions in route logic, or async errors forwarded to next(err).

Common situations: Malformed/expired auth tokens hitting decodeToken, storage adapter connectivity problems, programming bugs (undefined access, bad params not caught as ApiError), and 500s with empty error messages returning the raw err object.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/0865edc12cf66c74. Report an issue: GitHub.