{"record":{"id":"6686c9df2b878aa6","repo":"KeygraphHQ/shannon","slug":"deliverable-not-found","errorCode":"DELIVERABLE_NOT_FOUND","errorMessage":"Required deliverable file not found: ${file.paths.join(' or ')}","messagePattern":"Required deliverable file not found: (.+?)","errorType":"exception","errorClass":"PentestError","httpStatus":null,"severity":"error","filePath":"apps/worker/src/services/reporting.ts","lineNumber":65,"sourceCode":"    let added = false;\n    for (const candidate of file.paths) {\n      const filePath = path.join(dir, candidate);\n      try {\n        if (await fs.pathExists(filePath)) {\n          const content = await fs.readFile(filePath, 'utf8');\n          sections.push(content);\n          logger.info(`Added ${file.name} section from ${candidate}`);\n          added = true;\n          break;\n        }\n      } catch (error) {\n        const err = error as Error;\n        logger.warn(`Could not read ${candidate}: ${err.message}`);\n      }\n    }\n    if (!added) {\n      if (file.required) {\n        throw new PentestError(\n          `Required deliverable file not found: ${file.paths.join(' or ')}`,\n          'filesystem',\n          false,\n          { deliverableFile: file.paths, sourceDir },\n          ErrorCode.DELIVERABLE_NOT_FOUND,\n        );\n      }\n      logger.info(`No ${file.name} deliverable found`);\n    }\n  }\n\n  const finalContent = sections.join('\\n\\n');\n  const finalReportPath = path.join(dir, ASSEMBLED_REPORT_FILENAME);\n\n  try {\n    await fs.ensureDir(dir);\n    await fs.writeFile(finalReportPath, finalContent);\n    logger.info(`Final report assembled at ${finalReportPath}`);","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/KeygraphHQ/shannon/blob/1ae0a142f8525410a688f0309fd003cc5b1d92de/apps/worker/src/services/reporting.ts#L47-L83","documentation":"Thrown by assembleFinalReport when a deliverable file marked required=true is absent from the deliverables directory (none of its candidate paths in file.paths exist or are readable). Code DELIVERABLE_NOT_FOUND, category 'filesystem', non-retryable. In the current hardcoded deliverableFiles list every entry is required:false, so this is dormant unless a required entry is added or the list is extended.","triggerScenarios":"assembleFinalReport iterates its deliverableFiles list; for a required entry, if every candidate filename in file.paths is missing from deliverablesDir(sourceDir, deliverablesSubdir) or unreadable (the read error path at line 58 only logs a warning and continues, so it takes a true absence to fail), the throw fires. Today this requires editing the list to add a required:true file that an agent never produced.","commonSituations":"A maintainer adds a required deliverable to the reporting list but the producing agent was disabled (e.g. via vuln_classes scoping) or failed silently. The deliverables subdir path is wrong so all candidates resolve to a non-existent directory. A migration moved deliverables under .shannon/ but sourceDir was computed from the legacy run root.","solutions":["Confirm whether the entry should be required; in the shipped list all are optional, so verify the list was not modified.","Check context.deliverableFile and context.sourceDir on the thrown PentestError to see which file/dir was expected.","Ensure the agent that produces that deliverable ran successfully (session.metrics.agents) and that vuln_classes scoping did not exclude it.","If the file exists under .shannon/deliverables but sourceDir points elsewhere, fix the path resolution (deliverablesDir) or run the workspace migration.","Start a fresh workspace if the deliverable was never produced and cannot be regenerated."],"exampleFix":"// before: a required deliverable was added but agent disabled by scope\n//   { name: 'Auth', paths: ['auth_findings.md'], required: true }\n//   vuln_classes excludes 'auth' -> no file produced\n// after: either include the class or keep it optional\n//   { name: 'Auth', paths: ['auth_findings.md'], required: false }","handlingStrategy":"validation","validationCode":"// Before assembling the report, confirm any required deliverable is present\nimport { pathExists } from 'fs-extra';\nfor (const f of deliverableFiles) {\n  if (!f.required) continue;\n  const present = await Promise.any(f.paths.map((p) => pathExists(path.join(dir, p)).then((e) => { if (!e) throw 0; })));\n  if (!present) throw new Error(`Required deliverable missing: ${f.paths.join(' or ')}`);\n}","typeGuard":"function isDeliverableFile(o: unknown): o is DeliverableFile {\n  return typeof o === 'object' && o !== null &&\n    typeof (o as any).name === 'string' &&\n    Array.isArray((o as any).paths) &&\n    typeof (o as any).required === 'boolean';\n}","tryCatchPattern":"try {\n  await assembleFinalReport(sourceDir, deliverablesSubdir, logger);\n} catch (e) {\n  if (e instanceof PentestError && e.code === ErrorCode.DELIVERABLE_NOT_FOUND) {\n    // either the agent was scoped out (drop the entry) or never produced the file (re-run that phase)\n    const paths = (e.context as any)?.deliverableFile;\n    log.error('deliverable missing', { paths, sourceDir: (e.context as any)?.sourceDir });\n  }\n  throw e;\n}","preventionTips":["Only mark a deliverable required:true if its producing agent always runs (not affected by vuln_classes scoping).","Confirm the deliverables dir path (deliverablesDir) resolves under .shannon/deliverables for restructured workspaces.","Validate that completed agents in session.json each have their deliverable on disk before reporting.","Run a workspace migration before resume so deliverables are found at the expected path."],"tags":["reporting","deliverables","filesystem","config"],"backgroundTag":null,"analyzedSha":"1ae0a142f8525410a688f0309fd003cc5b1d92de","analyzedAt":"2026-08-12T17:40:03.583Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}