{"record":{"id":"76216bcfbdf46b65","repo":"can1357/oh-my-pi","slug":"rule-url-requires-a-rule-name-rule-name","errorCode":null,"errorMessage":"rule:// URL requires a rule name: rule://<name>","messagePattern":"rule:// URL requires a rule name: rule://<name>","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/rule-protocol.ts","lineNumber":19,"sourceCode":"/**\n * Protocol handler for rule:// URLs.\n *\n * URL forms:\n * - rule://<name> - Reads rule content\n */\nimport { getActiveRules } from \"../capability/rule\";\nimport type { InternalResource, InternalUrl, ProtocolHandler, UrlCompletion } from \"./types\";\n\nexport class RuleProtocolHandler implements ProtocolHandler {\n\treadonly scheme = \"rule\";\n\treadonly immutable = true;\n\n\tasync resolve(url: InternalUrl): Promise<InternalResource> {\n\t\tconst rules = getActiveRules();\n\n\t\tconst ruleName = url.rawHost || url.hostname;\n\t\tif (!ruleName) {\n\t\t\tthrow new Error(\"rule:// URL requires a rule name: rule://<name>\");\n\t\t}\n\n\t\tconst rule = rules.find(r => r.name === ruleName);\n\t\tif (!rule) {\n\t\t\tconst available = rules.map(r => r.name);\n\t\t\tconst availableStr = available.length > 0 ? available.join(\", \") : \"none\";\n\t\t\tthrow new Error(`Unknown rule: ${ruleName}\\nAvailable: ${availableStr}`);\n\t\t}\n\n\t\treturn {\n\t\t\turl: url.href,\n\t\t\tcontent: rule.content,\n\t\t\tcontentType: \"text/markdown\",\n\t\t\tsize: Buffer.byteLength(rule.content, \"utf-8\"),\n\t\t\tsourcePath: rule.path,\n\t\t\tnotes: [],\n\t\t};\n\t}","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/rule-protocol.ts#L1-L37","documentation":"The rule:// protocol resolves an active rule by name taken from url.rawHost || url.hostname. If the URL has no host component (e.g. bare 'rule://'), there is no rule name to look up and resolve() throws this usage error stating the expected form rule://<name>.","triggerScenarios":"Resolving 'rule://' with an empty host/hostname — missing the rule name segment entirely.","commonSituations":"Constructing the URL by string concatenation where the name variable was empty; completion/browse flows hitting the bare namespace URL.","solutions":["Include the rule name in the URL: rule://my-rule-name.","List available rule names first (getActiveRules() or the rule listing mechanism) and use one exactly.","Guard template construction so empty rule names never produce a bare rule:// URL."],"exampleFix":"// before\nconst url = `rule://${ruleName}`;\n// after\nif (!ruleName) throw new Error('rule name required');\nconst url = `rule://${encodeURIComponent(ruleName)}`;","handlingStrategy":"validation","validationCode":"if (!ruleName) throw new Error('rule:// URL requires a rule name: rule://<name>');\nconst url = `rule://${encodeURIComponent(ruleName)}`;","typeGuard":"const isValidRuleUrl = (u: string): boolean => /^rule:\\/\\/.+/i.test(u);","tryCatchPattern":"try {\n  return await router.resolve(url);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('requires a rule name')) throw new Error('supply rule://<name> with a non-empty rule name');\n  throw err;\n}","preventionTips":["Never build rule:// URLs from possibly-empty variables without a guard.","Enumerate rule names via getActiveRules() and validate membership before resolving.","URL-encode rule names so special characters do not break the host segment."],"tags":["internal-url","rules","missing-argument"],"backgroundTag":"missing-required-parameter","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}