{"record":{"id":"05b7e3263a7f4d67","repo":"garrytan/gstack","slug":"learnings-search-query-value-must-match","errorCode":null,"errorMessage":"{{LEARNINGS_SEARCH:query=...}} value must match ${QUERY_SAFE_RE} (alphanumeric, space, hyphen, underscore). Got: ${JSON.stringify(queryArg)}","messagePattern":"(.+?)\\} value must match (.+?) \\(alphanumeric, space, hyphen, underscore\\)\\. Got: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/resolvers/learnings.ts","lineNumber":31,"sourceCode":" */\nimport type { TemplateContext } from './types';\n\n// Whitelist for query= macro values. Allows alphanumeric, space, hyphen, underscore.\n// Anything else (e.g. $, backticks, quotes, ;) is a shell-injection vector when the\n// emitted bash interpolates the value into `--query \"${queryArg}\"`. Static template\n// queries hand-written in gstack are safe, but the resolver API must defend against\n// future contributors writing dangerous values.\nconst QUERY_SAFE_RE = /^[A-Za-z0-9 _-]+$/;\n\nexport function generateLearningsSearch(ctx: TemplateContext, args?: string[]): string {\n  // Parse query= arg. Empty value falls through to no-query (principle of least surprise:\n  // a stray {{LEARNINGS_SEARCH:query=}} placeholder gets today's behavior, not a build error).\n  const queryArg = (args || [])\n    .filter(a => a.startsWith('query='))\n    .map(a => a.slice(6))\n    .filter(Boolean)[0];\n  if (queryArg && !QUERY_SAFE_RE.test(queryArg)) {\n    throw new Error(\n      `{{LEARNINGS_SEARCH:query=...}} value must match ${QUERY_SAFE_RE} (alphanumeric, space, hyphen, underscore). Got: ${JSON.stringify(queryArg)}`\n    );\n  }\n  const queryFlag = queryArg ? ` --query \"${queryArg}\"` : '';\n\n  if (ctx.host === 'codex') {\n    // Codex: simpler version, no cross-project, uses $GSTACK_BIN\n    return `## Prior Learnings\n\nSearch for relevant learnings from previous sessions on this project:\n\n\\`\\`\\`bash\n$GSTACK_BIN/gstack-learnings-search --limit 10${queryFlag} 2>/dev/null || true\n\\`\\`\\`\n\nIf learnings are found, incorporate them into your analysis. When a review finding\nmatches a past learning, note it: \"Prior learning applied: [key] (confidence N, from [date])\"`;\n  }","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/scripts/resolvers/learnings.ts#L13-L49","documentation":"The {{LEARNINGS_SEARCH:query=...}} resolver in scripts/resolvers/learnings.ts:31 validates the query against QUERY_SAFE_RE (`^[A-Za-z0-9 _-]+$`). Anything else throws to prevent shell injection through the generated `--query` flag. An empty query= value is allowed (falls through to no-query).","triggerScenarios":"Using quotes, semicolons, ampersands, pipes, or backticks in the query= value. Curly/smart quotes pasted from a rich text editor. Non-ASCII characters.","commonSituations":"Pasting a query from documentation that was authored in a smart-quote editor. Including punctuation like `api & auth`.","solutions":["Restrict the query to ASCII alphanumerics, spaces, hyphens, and underscores","Drop punctuation from the query (e.g. `api auth` instead of `api & auth`)","URL-encode elsewhere if a richer query is needed (this resolver does not support encoding)"],"exampleFix":"<!-- before -->\n{{LEARNINGS_SEARCH:query=api & auth}}\n<!-- after -->\n{{LEARNINGS_SEARCH:query=api auth}}","handlingStrategy":"type-guard","validationCode":"const QUERY_SAFE_RE = /^[A-Za-z0-9 _-]+$/;\nif (query && !QUERY_SAFE_RE.test(query)) {\n  throw new Error(`query must match ${QUERY_SAFE_RE}`);\n}","typeGuard":"const QUERY_SAFE_RE = /^[A-Za-z0-9 _-]+$/;\nconst isSafeQuery = (q: string): boolean => QUERY_SAFE_RE.test(q);","tryCatchPattern":null,"preventionTips":["Hand-write queries using ASCII alphanumerics, spaces, hyphens, underscores only","Run a linter that catches smart quotes / non-ASCII in .tmpl files","Avoid punctuation like &, |, ;, quotes in learning queries"],"tags":["template","resolver","security","validation"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}