KeygraphHQ/shannon · error · PentestError
DELIVERABLE_NOT_FOUND
DELIVERABLE_NOT_FOUND
Error message
Required deliverable file not found: ${file.paths.join(' or ')} What it means
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.
Source
Thrown at apps/worker/src/services/reporting.ts:65
let added = false;
for (const candidate of file.paths) {
const filePath = path.join(dir, candidate);
try {
if (await fs.pathExists(filePath)) {
const content = await fs.readFile(filePath, 'utf8');
sections.push(content);
logger.info(`Added ${file.name} section from ${candidate}`);
added = true;
break;
}
} catch (error) {
const err = error as Error;
logger.warn(`Could not read ${candidate}: ${err.message}`);
}
}
if (!added) {
if (file.required) {
throw new PentestError(
`Required deliverable file not found: ${file.paths.join(' or ')}`,
'filesystem',
false,
{ deliverableFile: file.paths, sourceDir },
ErrorCode.DELIVERABLE_NOT_FOUND,
);
}
logger.info(`No ${file.name} deliverable found`);
}
}
const finalContent = sections.join('\n\n');
const finalReportPath = path.join(dir, ASSEMBLED_REPORT_FILENAME);
try {
await fs.ensureDir(dir);
await fs.writeFile(finalReportPath, finalContent);
logger.info(`Final report assembled at ${finalReportPath}`);View on GitHub (pinned to 1ae0a142f8)
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.
Example fix
// before: a required deliverable was added but agent disabled by scope
// { name: 'Auth', paths: ['auth_findings.md'], required: true }
// vuln_classes excludes 'auth' -> no file produced
// after: either include the class or keep it optional
// { name: 'Auth', paths: ['auth_findings.md'], required: false } Defensive patterns
Strategy: validation
Validate before calling
// Before assembling the report, confirm any required deliverable is present
import { pathExists } from 'fs-extra';
for (const f of deliverableFiles) {
if (!f.required) continue;
const present = await Promise.any(f.paths.map((p) => pathExists(path.join(dir, p)).then((e) => { if (!e) throw 0; })));
if (!present) throw new Error(`Required deliverable missing: ${f.paths.join(' or ')}`);
} Type guard
function isDeliverableFile(o: unknown): o is DeliverableFile {
return typeof o === 'object' && o !== null &&
typeof (o as any).name === 'string' &&
Array.isArray((o as any).paths) &&
typeof (o as any).required === 'boolean';
} Try / catch
try {
await assembleFinalReport(sourceDir, deliverablesSubdir, logger);
} catch (e) {
if (e instanceof PentestError && e.code === ErrorCode.DELIVERABLE_NOT_FOUND) {
// either the agent was scoped out (drop the entry) or never produced the file (re-run that phase)
const paths = (e.context as any)?.deliverableFile;
log.error('deliverable missing', { paths, sourceDir: (e.context as any)?.sourceDir });
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to write final report: ${err.message}
- CONFIG_NOT_FOUND
- CONFIG_VALIDATION_FAILED
- Cannot create deliverables directory at ${deliverablesDir}
- Login instructions template not found
AI-assisted analysis of KeygraphHQ/shannon@1ae0a142f8 (2026-08-12).
Data as JSON: /api/errors/6686c9df2b878aa6.
Report an issue: GitHub.