{"record":{"id":"b6a5010f7d1558f4","repo":"overleaf/overleaf","slug":"projectnotfound","errorCode":"ProjectNotFound","errorMessage":"project not found","messagePattern":"project not found","errorType":"error_code","errorClass":"CodedError","httpStatus":404,"severity":"error","filePath":"services/real-time/app/js/WebApiManager.js","lineNumber":44,"sourceCode":"        user: settings.apis.web.user,\n        password: settings.apis.web.pass,\n      },\n      json: {\n        userId,\n        anonymousAccessToken: user.anonymousAccessToken,\n      },\n    })\n  } catch (error) {\n    if (error instanceof RequestFailedError) {\n      if (error.response.status === 429) {\n        throw new CodedError(\n          'rate-limit hit when joining project',\n          'TooManyRequests'\n        )\n      } else if (error.response.status === 403) {\n        throw new NotAuthorizedError()\n      } else if (error.response.status === 404) {\n        throw new CodedError('project not found', 'ProjectNotFound')\n      }\n      throw new WebApiRequestFailedError(error.response.status)\n    }\n    throw OError.tag(error, 'join project request failed')\n  }\n  if (!(data && data.project)) {\n    throw new CorruptedJoinProjectResponseError()\n  }\n  const userMetadata = {\n    isRestrictedUser: data.isRestrictedUser,\n    isTokenMember: data.isTokenMember,\n    isInvitedMember: data.isInvitedMember,\n  }\n  return {\n    project: data.project,\n    privilegeLevel: data.privilegeLevel,\n    userMetadata,\n  }","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/real-time/app/js/WebApiManager.js#L26-L62","documentation":"joinProject throws CodedError('project not found', 'ProjectNotFound') when the upstream web API returns HTTP 404, and also when the successful response body lacks a project object (the `data && data.project` guard right after). It distinguishes 'no such project' from 'no permission' (403).","triggerScenarios":"Calling joinProject with a projectId that does not exist (deleted project, typo'd id), or a 200 response whose body omits the `project` field.","commonSituations":"Following a stale share link after the project was deleted or trashed; IDs copied from URLs of another environment (prod vs dev); races where the project is deleted between listing and joining; upstream API version changes altering the response shape.","solutions":["Confirm the projectId exists via the project list/API before joining","Refresh the share link from the project owner — the old one may point to a deleted project","Check you are hitting the correct environment (the id likely doesn't exist in this deployment)","Handle missing `project` in the response defensively if you control the upstream contract"],"exampleFix":"// before\nawait manager.joinProject(user, req.params.projectId)\n// after\nconst project = await ProjectLocator.promises.getProject(req.params.projectId)\nif (!project) return res.status(404).render('projectNotFound')\nawait manager.joinProject(user, req.params.projectId)","handlingStrategy":"type-guard","validationCode":"const project = await ProjectLocator.promises.getProject(projectId, { userId })\nif (!project) return res.status(404).render('projectNotFound') // before joining","typeGuard":"function hasProjectPayload(d: unknown): d is { project: Record<string, unknown> } {\n  return typeof d === 'object' && d !== null && 'project' in d && d.project != null\n}","tryCatchPattern":"try {\n  await manager.joinProject(user, projectId)\n} catch (err) {\n  if (err instanceof CodedError && err.code === 'ProjectNotFound') {\n    return res.redirect('/project/not-found')\n  }\n  throw err\n}","preventionTips":["Validate projectId format (e.g. ObjectId) before the call","Look up the project locally first to catch deletions early","Never reuse project ids across environments","Render friendly 404 pages instead of surfacing the raw error"],"tags":["http-404","not-found","real-time","project"],"backgroundTag":"resource-not-found","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}