{"record":{"id":"1a40ddaa3bf9e7da","repo":"hcengineering/platform","slug":"training-request-attachedto-not-found-1a40dd","errorCode":null,"errorMessage":"Training #${request.attachedTo} not found","messagePattern":"Training #(.+?) not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server-plugins/training-resources/src/functions/TrainingRequestTextPresenter.ts","lineNumber":29,"sourceCode":"//\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\nimport type { TriggerControl } from '@hcengineering/server-core'\nimport type { Presenter } from '@hcengineering/server-notification'\nimport type { TrainingRequest } from '@hcengineering/training'\nimport training from '@hcengineering/training'\n\n/** @public */\nexport const TrainingRequestTextPresenter: Presenter<TrainingRequest> = async (\n  request: TrainingRequest,\n  control: TriggerControl\n) => {\n  const trainingObject = (await control.findAll(control.ctx, training.class.Training, { _id: request.attachedTo }))[0]\n\n  if (trainingObject === undefined) {\n    throw new Error(`Training #${request.attachedTo} not found`)\n  }\n\n  return `${trainingObject.code} • ${trainingObject.title} • Revision ${trainingObject.revision}`\n}\n","sourceCodeStart":11,"sourceCodeEnd":34,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/server-plugins/training-resources/src/functions/TrainingRequestTextPresenter.ts#L11-L34","documentation":"TrainingRequestTextPresenter renders a human-readable text for a TrainingRequest by looking up the parent Training via request.attachedTo. If no Training object with that _id exists, it throws 'Training #<id> not found' because the presenter cannot produce a label without the parent document.","triggerScenarios":"Trigger control invokes the presenter function for a TrainingRequest whose attachedTo points at a Training that was deleted, or whose _id is invalid/never created (dangling reference).","commonSituations":"A Training was deleted while its TrainingRequests remained; data import/migration left orphaned requests; request created before the training commit landed; wrong attachedTo id from a client bug.","solutions":["Restore or recreate the missing Training with the referenced _id","Delete or reattach the orphaned TrainingRequest to a valid Training","Add cascading delete so removing a Training also removes its TrainingRequests","Make the presenter tolerant: return a fallback string instead of throwing for missing parents"],"exampleFix":"// before\nif (trainingObject === undefined) {\n  throw new Error(`Training #${request.attachedTo} not found`)\n}\n// after\nif (trainingObject === undefined) {\n  return `Unknown training (${request.attachedTo})`\n}","handlingStrategy":"validation","validationCode":"const parent = (await control.findAll(control.ctx, training.class.Training, { _id: request.attachedTo }))[0]\nif (parent === undefined) return `Unknown training (${request.attachedTo})`","typeGuard":"function trainingExists(t: Training | undefined): t is Training {\n  return t !== undefined\n}","tryCatchPattern":"try {\n  const text = await presenter(request, control)\n} catch (err) {\n  if (String((err as Error).message).endsWith('not found')) {\n    return `Unavailable training (${request.attachedTo})`\n  }\n  throw err\n}","preventionTips":["Enable cascading deletes: remove TrainingRequests when a Training is deleted","Run periodic orphan checks for requests whose attachedTo no longer resolves","Prefer a fallback label over throwing inside presenters, which run in trigger contexts","Validate attachedTo references on data import/migration"],"tags":["dangling-reference","trigger","presenter"],"backgroundTag":"document-not-found","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}