{"record":{"id":"6b75a74b6d588e20","repo":"NousResearch/hermes-agent","slug":"file-uploads-are-not-supported-against-oauth-gated","errorCode":null,"errorMessage":"File uploads are not supported against OAuth-gated remote backends yet.","messagePattern":"File uploads are not supported against OAuth-gated remote backends yet\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/main.ts","lineNumber":10879,"sourceCode":"  const timeoutMs = resolveTimeoutMs(request?.timeoutMs, DEFAULT_FETCH_TIMEOUT_MS)\n\n  const requestPath = pathWithGlobalRemoteProfile(request.path, profile, profileRouteOptions(profile))\n\n  const url = `${connection.baseUrl}${requestPath}`\n\n  // OAuth gateways authenticate REST via EITHER a native bearer token\n  // (cookieless RFC 8252 flow) OR the HttpOnly session cookie held in the OAuth\n  // partition. Prefer the native bearer when present (mirroring\n  // mintGatewayWsTicket): the native flow never sets a cookie, so routing an\n  // oauth-mode REST call through the cookie-only path returns 401 no_cookie even\n  // though a valid bearer is held. Cookie mode rides Electron's net stack bound\n  // to the OAuth partition so the cookie attaches automatically. Token/local\n  // modes keep using the static session-token header.\n  if (connection.authMode === 'oauth') {\n    // The OAuth path rides electron.net with JSON headers; multipart isn't\n    // wired there. Fail loudly rather than corrupting the upload.\n    if (request?.upload) {\n      throw new Error('File uploads are not supported against OAuth-gated remote backends yet.')\n    }\n\n    // Native bearer first (cookieless). ensureNativeAccessToken transparently\n    // refreshes a near-expiry AT via /auth/native/refresh; a null return means\n    // no native session (resolveOauthRestAuth then selects the cookie path).\n    const nativeAt = await ensureNativeAccessToken(connection.baseUrl).catch(() => null)\n    const restAuth = resolveOauthRestAuth(nativeAt)\n\n    if (restAuth.kind === 'bearer') {\n      return fetchJson(url, null, {\n        method: request?.method,\n        body: request?.body,\n        timeoutMs,\n        bearer: restAuth.token\n      })\n    }\n\n    return fetchJsonViaOauthSession(url, {","sourceCodeStart":10861,"sourceCodeEnd":10897,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/main.ts#L10861-L10897","documentation":"The desktop REST bridge supports three auth modes; for authMode 'oauth' it rides Electron's net stack with JSON headers bound to the OAuth session partition, and multipart file upload bodies are not implemented on that path. When a request carries request.upload against an OAuth-gated remote backend, it throws this explicit error instead of silently corrupting the upload. It is a known capability gap, deliberately failing loudly.","triggerScenarios":"Any renderer API call that sets request.upload (file attachment) while the active connection config has authMode 'oauth' — e.g. attaching a file to a chat message against a Hermes Cloud/OAuth remote backend.","commonSituations":"Users on OAuth-gated remote (cloud) backends trying the file-attach feature that works fine against token/local connections; code paths that assume the upload path is auth-mode-agnostic.","solutions":["Connect to that backend with a token (API token) instead of OAuth so uploads take the static-token fetch path","Upload against a local backend or via the CLI/gateway directly, then reference the uploaded artifact","If you own the code: implement multipart on the electron.net OAuth path (cookie or native bearer) — until then keep gating uploads on authMode !== 'oauth'","In renderer UI, disable/hide the attach button when connection.authMode === 'oauth' and show 'uploads unsupported on OAuth remotes'"],"exampleFix":"// before (renderer)\nawait ipc.invoke('hermes:api', { path: '/api/upload', upload: file })\n\n// after\nif (connection.authMode === 'oauth') throw new Error('Uploads unsupported on OAuth remote — connect with a token')\nawait ipc.invoke('hermes:api', { path: '/api/upload', upload: file })","handlingStrategy":"validation","validationCode":"if (request?.upload && connection.authMode === 'oauth') {\n  throw new Error('Upload not available on OAuth remote — connect with a token or use a local backend')\n}","typeGuard":"function uploadSupported(connection: { authMode: string }): boolean {\n  return connection.authMode === 'token' || connection.authMode === 'local'\n}","tryCatchPattern":"try { await api(path, { upload }) } catch (e) { if (e instanceof Error && e.message.includes('not supported against OAuth-gated')) notify('Switch to a token connection to upload files') else throw e }","preventionTips":["Gate the attach/upload UI on authMode !== 'oauth'","Prefer token-auth connections when file uploads are part of the workflow","Track the OAuth multipart gap as a feature flag rather than discovering it in production"],"tags":["oauth","file-upload","desktop","unsupported-feature","rest"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}