{"record":{"id":"fe17b5ee6f8253c7","repo":"paperclipai/paperclip","slug":"job-not-found","errorCode":null,"errorMessage":"Job not found","messagePattern":"Job not found","errorType":"http","errorClass":null,"httpStatus":404,"severity":"error","filePath":"server/src/routes/plugins.ts","lineNumber":2603,"sourceCode":"   * Errors: 404 if plugin not found\n   */\n  router.get(\"/plugins/:pluginId/jobs/:jobId/runs\", async (req, res) => {\n    assertBoardOrgAccess(req);\n    if (!jobDeps) {\n      res.status(501).json({ error: \"Job scheduling is not enabled\" });\n      return;\n    }\n\n    const { pluginId, jobId } = req.params;\n    const plugin = await resolvePlugin(registry, pluginId);\n    if (!plugin) {\n      res.status(404).json({ error: \"Plugin not found\" });\n      return;\n    }\n\n    const job = await jobDeps.jobStore.getJobByIdForPlugin(plugin.id, jobId);\n    if (!job) {\n      res.status(404).json({ error: \"Job not found\" });\n      return;\n    }\n\n    const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 25;\n    if (isNaN(limit) || limit < 1 || limit > 500) {\n      res.status(400).json({ error: \"limit must be a number between 1 and 500\" });\n      return;\n    }\n\n    try {\n      const runs = await jobDeps.jobStore.listRunsByJob(jobId, limit);\n      res.json(runs);\n    } catch (err) {\n      const message = err instanceof Error ? err.message : String(err);\n      res.status(500).json({ error: message });\n    }\n  });\n","sourceCodeStart":2585,"sourceCodeEnd":2621,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/routes/plugins.ts#L2585-L2621","documentation":"Returned as HTTP 404 by GET /api/plugins/:pluginId/jobs/:jobId/runs when the plugin exists but jobDeps.jobStore.getJobByIdForPlugin(plugin.id, jobId) finds no job — either the jobId is wrong or the job belongs to a different plugin. The plugin+job pair must both match.","triggerScenarios":"Using a jobId from another plugin, a deleted/expired job, or a copy-paste id with a typo or extra whitespace.","commonSituations":"Cross-plugin id confusion in scripts; UI state referencing a job deleted by a plugin reinstall; truncated UUIDs in logs.","solutions":["List the plugin's jobs with GET /api/plugins/:pluginId/jobs and copy the exact jobId","Confirm the jobId belongs to the plugin in the URL path, not just to the instance","If the job was deleted, re-create it (or the plugin's install flow) before querying runs"],"exampleFix":"// before\nGET /api/plugins/scheduler/jobs/job-from-notifier-plugin/runs\n// after\nGET /api/plugins/scheduler/jobs/8a2f.../runs  // id returned by /jobs list","handlingStrategy":"validation","validationCode":"const jobs = await (await fetch(`/api/plugins/${pluginId}/jobs`)).json();\nif (!jobs.some(j => j.id === jobId)) throw new Error(`Job ${jobId} not found for plugin ${pluginId}`);","typeGuard":null,"tryCatchPattern":"try { await listJobRuns(pluginId, jobId, limit); } catch (e) { if (e.status === 404 && /Job not found/.test(e.body.error)) refreshJobList(pluginId); else throw e; }","preventionTips":["Always fetch job ids from the plugin's own /jobs listing","Trim whitespace from copied ids; never hand-edit UUIDs","After plugin reinstall, re-resolve job ids before showing run history"],"tags":["http-404","plugins","jobs","not-found"],"backgroundTag":"resource-not-found","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}