{"record":{"id":"9009f129d8950f8f","repo":"KeygraphHQ/shannon","slug":"failed-to-build-login-instructions-errmsg","errorCode":null,"errorMessage":"Failed to build login instructions: ${errMsg}","messagePattern":"Failed to build login instructions: (.+?)","errorType":"exception","errorClass":"PentestError","httpStatus":null,"severity":"error","filePath":"apps/worker/src/services/prompt-manager.ts","lineNumber":243,"sourceCode":"          `generated TOTP code using secret \"${authentication.credentials.email_login.totp_secret}\"`,\n        );\n      }\n    }\n\n    loginInstructions = replaceLiteral(loginInstructions, /{{user_instructions}}/g, userInstructions);\n\n    // 5. Replace TOTP secret placeholder if present in template\n    if (authentication.credentials?.totp_secret) {\n      loginInstructions = replaceLiteral(loginInstructions, /{{totp_secret}}/g, authentication.credentials.totp_secret);\n    }\n\n    return loginInstructions;\n  } catch (error) {\n    if (error instanceof PentestError) {\n      throw error;\n    }\n    const errMsg = error instanceof Error ? error.message : String(error);\n    throw new PentestError(`Failed to build login instructions: ${errMsg}`, 'config', false, {\n      authentication,\n      originalError: errMsg,\n    });\n  }\n}\n\n// Pure function: Process @include() directives\nasync function processIncludes(content: string, baseDir: string): Promise<string> {\n  const includeRegex = /@include\\(([^)]+)\\)/g;\n  const resolvedBase = path.resolve(baseDir);\n\n  const replacements: IncludeReplacement[] = await Promise.all(\n    Array.from(content.matchAll(includeRegex)).map(async (match) => {\n      const rawPath = match[1] ?? '';\n      const includePath = path.resolve(baseDir, rawPath);\n      if (!includePath.startsWith(resolvedBase + path.sep) && includePath !== resolvedBase) {\n        throw new PentestError(`Path traversal detected in @include(): ${rawPath}`, 'prompt', false, {\n          includePath,","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/KeygraphHQ/shannon/blob/1ae0a142f8525410a688f0309fd003cc5b1d92de/apps/worker/src/services/prompt-manager.ts#L225-L261","documentation":"Catch-all wrapper in buildLoginInstructions for any non-PentestError exception raised while assembling login instructions (e.g. fs.readFile EACCES/ENOENT after the pathExists check, a replaceLiteral regex failure, or a malformed credentials object). It re-throws PentestError instances unchanged and wraps everything else with category 'config', non-retryable, carrying the original error message and the full authentication config object in context.","triggerScenarios":"An authentication config passes the template-exists check at line 162 but a later operation throws a plain Error: the template file becomes unreadable between the check and read (race), a credentials field has an unexpected type that breaks replaceLiteral, or the login_flow array contains a non-string. The outer catch converts it into this PentestError.","commonSituations":"Permissions on the prompts dir change mid-run. A hand-edited YAML config supplies credentials with wrong shape (e.g. username as a number) that survives schema validation but breaks string substitution. Concurrent workspace migration moving the template file.","solutions":["Read the context.originalError and context.authentication fields from the thrown PentestError to identify the underlying cause.","Validate the authentication.credentials object shape against the config schema (apps/worker/configs/ config-schema.json) before starting the scan.","Confirm the shared/login-instructions.txt file is readable (not just present) inside the worker container.","Re-run after correcting the malformed config field named in originalError."],"exampleFix":"// before: credentials.username is a number, replaceLiteral receives non-string\n//   authentication:\n//     credentials:\n//       username: 1234\n// after: supply strings as the schema requires\n//   authentication:\n//     credentials:\n//       username: \"1234\"","handlingStrategy":"try-catch","validationCode":"// Validate the credentials object shape before the scan starts\nfunction validCredentials(c: any): boolean {\n  if (!c) return true; // credentials optional\n  return ['username', 'password', 'totp_secret'].every((k) => c[k] === undefined || typeof c[k] === 'string') &&\n    (c.email_login === undefined || (typeof c.email_login?.address === 'string' || c.email_login === undefined));\n}\nif (config.authentication && !validCredentials(config.authentication.credentials)) {\n  throw new Error('credentials fields must be strings');\n}","typeGuard":"function isStringRecord(o: unknown, keys: readonly string[]): boolean {\n  return typeof o === 'object' && o !== null && keys.every((k) => k in o ? typeof (o as any)[k] === 'string' : true);\n}","tryCatchPattern":"try {\n  await loadPrompt(name, vars, config, false, logger);\n} catch (e) {\n  if (e instanceof PentestError && /Failed to build login instructions/.test(e.message)) {\n    const original = (e.context as any)?.originalError;\n    log.error('login build failed', { original, auth: (e.context as any)?.authentication });\n  }\n  throw e;\n}","preventionTips":["Always supply credential fields as strings per the schema; never numbers or objects.","Run the YAML config through the JSON-schema validator (config-parser) before the scan.","Treat context.originalError as the source of truth when this wrapper fires.","Keep the login template readable (not just present) throughout the run."],"tags":["prompt","authentication","config","validation","error-wrapping"],"backgroundTag":null,"analyzedSha":"1ae0a142f8525410a688f0309fd003cc5b1d92de","analyzedAt":"2026-08-12T17:40:03.583Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}