[BUG] Immich CLI can fail partway through long upload with AxiosError: read ENETUNREACH #1816

Closed
opened 2026-02-05 03:58:38 +03:00 by OVERLORD · 1 comment
Owner

Originally created by @anoadragon453 on GitHub (Dec 17, 2023).

The bug

The Immich CLI upload command will bail out if an AxiosError: read ENETUNREACH is raised. This causes the program to exit, resulting in a long upload process being interrupted.

While uploading ~100GB of pictures and videos overnight, I came back in the morning to find that only ~37% had actually been uploaded before the command exited early.

Click to view the command and full output
➜ npx ts-node . upload --recursive /home/user/Pictures
███████████████░░░░░░░░░░░░░░░░░░░░░░░░░ | 37% | ETA: 9h5m | 38.3 GB/101.6 GB: /home/user/Pictures/Camera/2022/10/VID_20221025_233224.mp4
/home/user/code/immich/cli/node_modules/axios/lib/core/AxiosError.js:89
  AxiosError.call(axiosError, error.message, code, config, request, response);
             ^
AxiosError: read ENETUNREACH
    at Function.AxiosError.from (/home/user/code/immich/cli/node_modules/axios/lib/core/AxiosError.js:89:14)
    at ClientRequest.handleRequestError (/home/user/code/immich/cli/node_modules/axios/lib/adapters/http.js:606:25)
    at ClientRequest.emit (node:events:526:35)
    at ClientRequest.emit (node:domain:488:12)
    at TLSSocket.socketErrorListener (node:_http_client:495:9)
    at TLSSocket.emit (node:events:514:28)
    at TLSSocket.emit (node:domain:488:12)
    at emitErrorNT (node:internal/streams/destroy:151:8)
    at emitErrorCloseNT (node:internal/streams/destroy:116:3)
    at processTicksAndRejections (node:internal/process/task_queues:82:21) {
  syscall: 'read',
  code: 'ENETUNREACH',
  errno: -101,
  config: {
    transitional: {
      silentJSONParsing: true,
      forcedJSONParsing: true,
      clarifyTimeoutError: false
    },
    adapter: [ 'xhr', 'http' ],
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: Infinity,
    maxBodyLength: Infinity,
    env: { FormData: [Function], Blob: [class Blob] },
    validateStatus: [Function: validateStatus],
    headers: Object [AxiosHeaders] {
      Accept: 'application/json, text/plain, */*',
      'Content-Type': 'multipart/form-data; boundary=--------------------------679382546755667818894717',
      'x-api-key': '5EyE3zZZAlOKwYHivZvUlRZ7bdaTL95lpCzT7Iijues',
      'User-Agent': 'axios/1.6.2',
      'Content-Length': '252377779',
      'Accept-Encoding': 'gzip, compress, deflate, br'
    },
    method: 'post',
    maxRedirects: 0,
    url: 'https://i.amorgan.xyz/api/asset/upload',
    data: FormData {
      _overheadLength: 722,
      _valueLength: 89,
      _valuesToMeasure: [Array],
      writable: true,
      readable: true,
      dataSize: 0,
      maxDataSize: 2097152,
      pauseStreams: true,
      _released: true,
      _streams: [Array],
      _currentStream: [DelayedStream],
      _insideLoop: false,
      _pendingNext: false,
      _boundary: '--------------------------679382546755667818894717',
      _events: [Object: null prototype],
      _eventsCount: 4
    }
  },
  request: <ref *1> ClientRequest {
    _events: [Object: null prototype] {
      response: [Function],
      error: [Function: handleRequestError],
      socket: [Function: handleRequestSocket]
    },
    _eventsCount: 3,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    destroyed: false,
    _last: false,
    chunkedEncoding: false,
    shouldKeepAlive: true,
    maxRequestsOnConnectionReached: false,
    _defaultKeepAlive: true,
    useChunkedEncodingByDefault: true,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    strictContentLength: false,
    _contentLength: '252377779',
    _hasBody: true,
    _trailer: '',
    finished: false,
    _headerSent: true,
    _closed: false,
    socket: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      secureConnecting: false,
      _SNICallback: null,
      servername: 'i.amorgan.xyz',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 10,
      connecting: false,
      _hadError: true,
      _parent: null,
      _host: 'i.amorgan.xyz',
      _closeAfterHandlingError: false,
      _readableState: [ReadableState],
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: null,
      _requestCert: true,
      _rejectUnauthorized: true,
      timeout: 5000,
      parser: null,
      _httpMessage: [Circular *1],
      [Symbol(alpncallback)]: null,
      [Symbol(res)]: [TLSWrap],
      [Symbol(verified)]: true,
      [Symbol(pendingSession)]: null,
      [Symbol(async_id_symbol)]: 1915656,
      [Symbol(kHandle)]: null,
      [Symbol(lastWriteQueueSize)]: 65624,
      [Symbol(timeout)]: [Timeout],
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kSetNoDelay)]: false,
      [Symbol(kSetKeepAlive)]: true,
      [Symbol(kSetKeepAliveInitialDelay)]: 60,
      [Symbol(kBytesRead)]: 118998,
      [Symbol(kBytesWritten)]: 654680937,
      [Symbol(connect-options)]: [Object]
    },
    _header: 'POST /api/asset/upload HTTP/1.1\r\n' +
      'Accept: application/json, text/plain, */*\r\n' +
      'Content-Type: multipart/form-data; boundary=--------------------------679382546755667818894717\r\n' +
      'x-api-key: 5EyE3zZZAlOKwYHivZvUlRZ7bdaTL95lpCzT7Iijues\r\n' +
      'User-Agent: axios/1.6.2\r\n' +
      'Content-Length: 252377779\r\n' +
      'Accept-Encoding: gzip, compress, deflate, br\r\n' +
      'Host: i.amorgan.xyz\r\n' +
      'Connection: keep-alive\r\n' +
      '\r\n',
    _keepAliveTimeout: 0,
    _onPendingData: [Function: nop],
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 443,
      protocol: 'https:',
      options: [Object: null prototype],
      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,
      maxCachedSessions: 100,
      _sessionCache: [Object],
      [Symbol(kCapture)]: false
    },
    socketPath: undefined,
    method: 'POST',
    maxHeaderSize: undefined,
    insecureHTTPParser: undefined,
    joinDuplicateHeaders: undefined,
    path: '/api/asset/upload',
    _ended: false,
    res: null,
    aborted: false,
    timeoutCb: [Function: emitRequestTimeout],
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: true,
    host: 'i.amorgan.xyz',
    protocol: 'https:',
    [Symbol(kCapture)]: false,
    [Symbol(kBytesWritten)]: 0,
    [Symbol(kNeedDrain)]: true,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      accept: [Array],
      'content-type': [Array],
      'x-api-key': [Array],
      'user-agent': [Array],
      'content-length': [Array],
      'accept-encoding': [Array],
      host: [Array]
    },
    [Symbol(errored)]: null,
    [Symbol(kHighWaterMark)]: 16384,
    [Symbol(kRejectNonStandardBodyWrites)]: false,
    [Symbol(kUniqueHeaders)]: null
  },
  cause: Error: read ENETUNREACH
      at TLSWrap.onStreamRead (node:internal/stream_base_commons:217:20) {
    errno: -101,
    code: 'ENETUNREACH',
    syscall: 'read'
  }
}

This appeared to have happened during a momentary blip in either my local internet or that of the server. Ideally the API request that failed would have been retried again by the client until it succeeded, allowing the upload to continue.

This was with the Immich CLI ran from source, commit 0aae9696f6 (tag v1.91.1).

The OS that Immich Server is running on

NixOS 23.11

Version of Immich Server

v1.91.0

Version of Immich Mobile App

N/A

Platform with the issue

  • Server
  • Web
  • Mobile

Your docker-compose.yml content

N/A

Your .env content

N/A

Reproduction steps

1. Begin a large upload with the Immich CLI: `immich upload --recursive path/to/photos`.
2. Wait for the command to fail partway through with `AxiosError: read ENETUNREACH`.

Additional information

No response

Originally created by @anoadragon453 on GitHub (Dec 17, 2023). ### The bug The Immich CLI `upload` command will bail out if an `AxiosError: read ENETUNREACH` is raised. This causes the program to exit, resulting in a long upload process being interrupted. While uploading ~100GB of pictures and videos overnight, I came back in the morning to find that only ~37% had actually been uploaded before the command exited early. <details> <summary>Click to view the command and full output</summary> ``` ➜ npx ts-node . upload --recursive /home/user/Pictures ███████████████░░░░░░░░░░░░░░░░░░░░░░░░░ | 37% | ETA: 9h5m | 38.3 GB/101.6 GB: /home/user/Pictures/Camera/2022/10/VID_20221025_233224.mp4 /home/user/code/immich/cli/node_modules/axios/lib/core/AxiosError.js:89 AxiosError.call(axiosError, error.message, code, config, request, response); ^ AxiosError: read ENETUNREACH at Function.AxiosError.from (/home/user/code/immich/cli/node_modules/axios/lib/core/AxiosError.js:89:14) at ClientRequest.handleRequestError (/home/user/code/immich/cli/node_modules/axios/lib/adapters/http.js:606:25) at ClientRequest.emit (node:events:526:35) at ClientRequest.emit (node:domain:488:12) at TLSSocket.socketErrorListener (node:_http_client:495:9) at TLSSocket.emit (node:events:514:28) at TLSSocket.emit (node:domain:488:12) at emitErrorNT (node:internal/streams/destroy:151:8) at emitErrorCloseNT (node:internal/streams/destroy:116:3) at processTicksAndRejections (node:internal/process/task_queues:82:21) { syscall: 'read', code: 'ENETUNREACH', errno: -101, config: { transitional: { silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false }, adapter: [ 'xhr', 'http' ], transformRequest: [ [Function: transformRequest] ], transformResponse: [ [Function: transformResponse] ], timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: Infinity, maxBodyLength: Infinity, env: { FormData: [Function], Blob: [class Blob] }, validateStatus: [Function: validateStatus], headers: Object [AxiosHeaders] { Accept: 'application/json, text/plain, */*', 'Content-Type': 'multipart/form-data; boundary=--------------------------679382546755667818894717', 'x-api-key': '5EyE3zZZAlOKwYHivZvUlRZ7bdaTL95lpCzT7Iijues', 'User-Agent': 'axios/1.6.2', 'Content-Length': '252377779', 'Accept-Encoding': 'gzip, compress, deflate, br' }, method: 'post', maxRedirects: 0, url: 'https://i.amorgan.xyz/api/asset/upload', data: FormData { _overheadLength: 722, _valueLength: 89, _valuesToMeasure: [Array], writable: true, readable: true, dataSize: 0, maxDataSize: 2097152, pauseStreams: true, _released: true, _streams: [Array], _currentStream: [DelayedStream], _insideLoop: false, _pendingNext: false, _boundary: '--------------------------679382546755667818894717', _events: [Object: null prototype], _eventsCount: 4 } }, request: <ref *1> ClientRequest { _events: [Object: null prototype] { response: [Function], error: [Function: handleRequestError], socket: [Function: handleRequestSocket] }, _eventsCount: 3, _maxListeners: undefined, outputData: [], outputSize: 0, writable: true, destroyed: false, _last: false, chunkedEncoding: false, shouldKeepAlive: true, maxRequestsOnConnectionReached: false, _defaultKeepAlive: true, useChunkedEncodingByDefault: true, sendDate: false, _removedConnection: false, _removedContLen: false, _removedTE: false, strictContentLength: false, _contentLength: '252377779', _hasBody: true, _trailer: '', finished: false, _headerSent: true, _closed: false, socket: TLSSocket { _tlsOptions: [Object], _secureEstablished: true, _securePending: false, _newSessionPending: false, _controlReleased: true, secureConnecting: false, _SNICallback: null, servername: 'i.amorgan.xyz', alpnProtocol: false, authorized: true, authorizationError: null, encrypted: true, _events: [Object: null prototype], _eventsCount: 10, connecting: false, _hadError: true, _parent: null, _host: 'i.amorgan.xyz', _closeAfterHandlingError: false, _readableState: [ReadableState], _maxListeners: undefined, _writableState: [WritableState], allowHalfOpen: false, _sockname: null, _pendingData: null, _pendingEncoding: '', server: undefined, _server: null, ssl: null, _requestCert: true, _rejectUnauthorized: true, timeout: 5000, parser: null, _httpMessage: [Circular *1], [Symbol(alpncallback)]: null, [Symbol(res)]: [TLSWrap], [Symbol(verified)]: true, [Symbol(pendingSession)]: null, [Symbol(async_id_symbol)]: 1915656, [Symbol(kHandle)]: null, [Symbol(lastWriteQueueSize)]: 65624, [Symbol(timeout)]: [Timeout], [Symbol(kBuffer)]: null, [Symbol(kBufferCb)]: null, [Symbol(kBufferGen)]: null, [Symbol(kCapture)]: false, [Symbol(kSetNoDelay)]: false, [Symbol(kSetKeepAlive)]: true, [Symbol(kSetKeepAliveInitialDelay)]: 60, [Symbol(kBytesRead)]: 118998, [Symbol(kBytesWritten)]: 654680937, [Symbol(connect-options)]: [Object] }, _header: 'POST /api/asset/upload HTTP/1.1\r\n' + 'Accept: application/json, text/plain, */*\r\n' + 'Content-Type: multipart/form-data; boundary=--------------------------679382546755667818894717\r\n' + 'x-api-key: 5EyE3zZZAlOKwYHivZvUlRZ7bdaTL95lpCzT7Iijues\r\n' + 'User-Agent: axios/1.6.2\r\n' + 'Content-Length: 252377779\r\n' + 'Accept-Encoding: gzip, compress, deflate, br\r\n' + 'Host: i.amorgan.xyz\r\n' + 'Connection: keep-alive\r\n' + '\r\n', _keepAliveTimeout: 0, _onPendingData: [Function: nop], agent: Agent { _events: [Object: null prototype], _eventsCount: 2, _maxListeners: undefined, defaultPort: 443, protocol: 'https:', options: [Object: null prototype], 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, maxCachedSessions: 100, _sessionCache: [Object], [Symbol(kCapture)]: false }, socketPath: undefined, method: 'POST', maxHeaderSize: undefined, insecureHTTPParser: undefined, joinDuplicateHeaders: undefined, path: '/api/asset/upload', _ended: false, res: null, aborted: false, timeoutCb: [Function: emitRequestTimeout], upgradeOrConnect: false, parser: null, maxHeadersCount: null, reusedSocket: true, host: 'i.amorgan.xyz', protocol: 'https:', [Symbol(kCapture)]: false, [Symbol(kBytesWritten)]: 0, [Symbol(kNeedDrain)]: true, [Symbol(corked)]: 0, [Symbol(kOutHeaders)]: [Object: null prototype] { accept: [Array], 'content-type': [Array], 'x-api-key': [Array], 'user-agent': [Array], 'content-length': [Array], 'accept-encoding': [Array], host: [Array] }, [Symbol(errored)]: null, [Symbol(kHighWaterMark)]: 16384, [Symbol(kRejectNonStandardBodyWrites)]: false, [Symbol(kUniqueHeaders)]: null }, cause: Error: read ENETUNREACH at TLSWrap.onStreamRead (node:internal/stream_base_commons:217:20) { errno: -101, code: 'ENETUNREACH', syscall: 'read' } } ``` </details> This appeared to have happened during a momentary blip in either my local internet or that of the server. Ideally the API request that failed would have been retried again by the client until it succeeded, allowing the upload to continue. This was with the Immich CLI ran from source, commit 0aae9696f68b830d918780d9c3ffca74352adea9 (tag v1.91.1). ### The OS that Immich Server is running on NixOS 23.11 ### Version of Immich Server v1.91.0 ### Version of Immich Mobile App N/A ### Platform with the issue - [ ] Server - [ ] Web - [ ] Mobile ### Your docker-compose.yml content ```YAML N/A ``` ### Your .env content ```Shell N/A ``` ### Reproduction steps ```bash 1. Begin a large upload with the Immich CLI: `immich upload --recursive path/to/photos`. 2. Wait for the command to fail partway through with `AxiosError: read ENETUNREACH`. ``` ### Additional information _No response_
OVERLORD added the cli label 2026-02-05 03:58:38 +03:00
Author
Owner

@etnoy commented on GitHub (Dec 19, 2023):

We currently do not have a retry function when performing CLI uploads. However, restarting the upload should resume from where you were after deduplication is done. In some cases skipping hashing makes this process go faster (deduplication is always done server-side)

@etnoy commented on GitHub (Dec 19, 2023): We currently do not have a retry function when performing CLI uploads. However, restarting the upload should resume from where you were after deduplication is done. In some cases skipping hashing makes this process go faster (deduplication is always done server-side)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: immich-app/immich#1816