KeygraphHQ/shannon · error · PentestError

Variables must include webUrl and repoPath

Error message

Variables must include webUrl and repoPath

What it means

Second validation guard in interpolateVariables: the variables object must be truthy and contain both webUrl and repoPath (the two placeholders {{WEB_URL}} and {{REPO_PATH}} are always substituted). Missing either throws with category 'validation', non-retryable, recording which keys were present in context.variables. This is an internal-contract check on the caller (loadPrompt builds these from CLI/pipeline input).

Source

Thrown at apps/worker/src/services/prompt-manager.ts:325

// Pure function: Variable interpolation
async function interpolateVariables(
  template: string,
  variables: PromptVariables,
  config: DistributedConfig | null = null,
  logger: ActivityLogger,
  promptsBaseDir: string = PROMPTS_DIR,
): Promise<string> {
  try {
    if (!template || typeof template !== 'string') {
      throw new PentestError('Template must be a non-empty string', 'validation', false, {
        templateType: typeof template,
        templateLength: template?.length,
      });
    }

    if (!variables || !variables.webUrl || !variables.repoPath) {
      throw new PentestError('Variables must include webUrl and repoPath', 'validation', false, {
        variables: Object.keys(variables || {}),
      });
    }

    // replaceLiteral is used for all value insertions so config values that
    // contain `$&`/`$$`/`$1`/etc. aren't mangled as replacement patterns.
    let result = template;
    result = replaceLiteral(result, /{{WEB_URL}}/g, variables.webUrl);
    result = replaceLiteral(result, /{{REPO_PATH}}/g, variables.repoPath);
    result = replaceLiteral(result, /{{PLAYWRIGHT_SESSION}}/g, variables.PLAYWRIGHT_SESSION || 'agent1');
    result = replaceLiteral(result, /{{AUTH_CONTEXT}}/g, buildAuthContext(config));
    result = replaceLiteral(
      result,
      /{{DESCRIPTION}}/g,
      config?.description ? `Description: ${config.description}` : '',
    );

    const avoidUrlRules = config?.avoid?.filter((r) => r.type !== 'code_path') ?? [];

View on GitHub (pinned to 1ae0a142f8)

Solutions

  1. Inspect context.variables on the thrown PentestError to see which keys were supplied.
  2. Ensure the scan start command passes both -u <url> and -r <repo> so the pipeline receives webUrl and repoPath.
  3. If resuming, verify session.json contains session.webUrl and that the repo mount is present before the workflow restarts.
  4. Patch the caller to default/validate these fields before calling loadPrompt.

Example fix

// before: variables missing repoPath
//   await loadPrompt('recon', { webUrl: 'https://t' }, config, false, logger);
// after: supply both required fields
//   await loadPrompt('recon', { webUrl: 'https://t', repoPath: '/workspace/repo' }, config, false, logger);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure both required variables are present before loading a prompt
function hasRequiredVars(v: Partial<PromptVariables>): v is PromptVariables {
  return typeof v.webUrl === 'string' && v.webUrl.length > 0 &&
    typeof v.repoPath === 'string' && v.repoPath.length > 0;
}
if (!hasRequiredVars(variables)) {
  throw new Error(`Missing required prompt variables: webUrl/repoPath`);
}

Type guard

function hasWebUrlAndRepoPath(v: unknown): v is { webUrl: string; repoPath: string } {
  return typeof v === 'object' && v !== null &&
    typeof (v as any).webUrl === 'string' && (v as any).webUrl.length > 0 &&
    typeof (v as any).repoPath === 'string' && (v as any).repoPath.length > 0;
}

Try / catch

try {
  await loadPrompt(name, variables, config, false, logger);
} catch (e) {
  if (e instanceof PentestError && /Variables must include webUrl and repoPath/.test(e.message)) {
    // pipeline-input reconstruction is incomplete — rebuild vars from session.json
    variables = { webUrl: session.session.webUrl, repoPath: expectedRepoPath, ...variables };
  }
  throw e;
}

Prevention

When it happens

Trigger: loadPrompt (or a direct interpolateVariables caller) is invoked with a PromptVariables object where webUrl or repoPath is undefined/empty — e.g. the pipeline started without a target URL, the repo path was not mounted, or a resume path reconstructed variables incompletely.

Common situations: A pipeline-input bug omits repoPath when running a phase that does not need the repo but still interpolates prompts. Resume logic rebuilds variables from session.json but a field was never persisted. A test harness calls loadPrompt with a partial variables object.

Related errors


AI-assisted analysis of KeygraphHQ/shannon@1ae0a142f8 (2026-08-12). Data as JSON: /api/errors/76dac083420a0bff. Report an issue: GitHub.