{"record":{"id":"bf2f561b8a5bee1e","repo":"bytedance/deer-flow","slug":"unable-to-recover-sse-history-after-recoveryatte","errorCode":null,"errorMessage":"Unable to recover SSE history after ${recoveryAttempts} attempts (requested ${gap.requested_event_id ?? \"initial stream\"}, earliest ${gap.earliest_available_event_id})","messagePattern":"Unable to recover SSE history after (.+?) attempts \\(requested (.+?), earliest (.+?)\\)","errorType":"exception","errorClass":"StreamReplayGapError","httpStatus":null,"severity":"error","filePath":"frontend/src/core/api/api-client.ts","lineNumber":273,"sourceCode":"      if (entry.event === \"gap\") {\n        gap = parseStreamReplayGap(entry.data);\n        break;\n      }\n      yield entry;\n    }\n\n    if (!gap) {\n      return;\n    }\n\n    const runId = expectedRunId() ?? gap.run_id;\n    if (!threadId || gap.run_id !== runId) {\n      throw new Error(\n        \"Stream replay gap does not match the active thread run.\",\n      );\n    }\n    if (recoveryAttempts >= MAX_STREAM_GAP_RECOVERIES) {\n      throw new StreamReplayGapError(gap, recoveryAttempts);\n    }\n    recoveryAttempts += 1;\n\n    // The SDK would otherwise ignore an unknown `gap` event and report a\n    // normal finish. Surface a custom control event to DeerFlow's hook, reload\n    // durable values, then explicitly follow only events newer than the\n    // retained tail captured by the server.\n    clearReconnectRun(threadId, runId);\n    yield {\n      event: \"custom\",\n      data: { type: \"stream_replay_gap\", ...gap },\n    };\n\n    const durableState = await client.threads\n      .getState(threadId)\n      .catch((error: unknown) => {\n        throw new StreamReplayGapError(gap, recoveryAttempts, error);\n      });","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/frontend/src/core/api/api-client.ts#L255-L291","documentation":"Raised when owner_user_id is empty after strip(). Trusted-mode removal made owner_user_id mandatory: every memory operation is scoped to an owning user, so the backend refuses to construct a config that would attribute memories to nobody.","triggerScenarios":"Omitting owner_user_id from the openviking backend_config, or setting it to an empty or whitespace-only string (often via an unset template expansion). Detected during _validate at config load.","commonSituations":"Migrating from trusted-mode config that had no owner concept; per-user deployments where a template variable was supposed to inject the id but expanded empty; deleting the line while editing.","solutions":["Set owner_user_id to the real owning user identifier, e.g. owner_user_id: user-42.","If the value comes from templating, verify the variable expands and fail at template time when unset.","Restart or reload the Gateway after fixing config.yaml."],"exampleFix":"# before\nbackend_config:\n  api_key_env: OPENVIKING_API_KEY\n\n# after\nbackend_config:\n  api_key_env: OPENVIKING_API_KEY\n  owner_user_id: user-42","handlingStrategy":"validation","validationCode":"def check_owner_user_id(value: str) -> None:\n    assert value.strip(), 'owner_user_id must be set (trusted mode is gone)'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make owner_user_id a required field in your deployment manifest so templating fails when it is missing.","Never let ${...} expansions collapse to empty for identity fields.","Assign one owner per user or tenant rather than sharing a blank owner."],"tags":["openviking","configuration","validation","auth"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}