{"record":{"id":"cfbe094a1e76da5a","repo":"can1357/oh-my-pi","slug":"invalid-skill-url-path-encoding-url","errorCode":null,"errorMessage":"Invalid skill:// URL path encoding: ${url}","messagePattern":"Invalid skill:// URL path encoding: (.+?)","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/bash-skill-urls.ts","lineNumber":92,"sourceCode":"\tif (!skill) {\n\t\tconst available = skills.map(s => s.name);\n\t\tconst availableStr = available.length > 0 ? available.join(\", \") : \"none\";\n\t\tthrow new ToolError(`Unknown skill: ${rawSkillSegment}. Available: ${availableStr}`);\n\t}\n\n\t// Combine any colon suffix (line range like \":1-5\") with the path segment\n\tconst rawPath = (parsed[2] ?? \"\") + (suffix ? `/${suffix}` : \"\");\n\tconst hasRelativePath = rawPath !== \"\" && rawPath !== \"/\";\n\n\tif (!hasRelativePath) {\n\t\treturn path.resolve(skill.baseDir);\n\t}\n\n\tlet relativePath: string;\n\ttry {\n\t\trelativePath = decodeURIComponent(rawPath.slice(1));\n\t} catch {\n\t\tthrow new ToolError(`Invalid skill:// URL path encoding: ${url}`);\n\t}\n\ttry {\n\t\tvalidateRelativePath(relativePath);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new ToolError(message);\n\t}\n\n\tconst targetPath = path.join(skill.baseDir, relativePath);\n\tconst resolvedPath = path.resolve(targetPath);\n\tconst resolvedBaseDir = path.resolve(skill.baseDir);\n\tif (!resolvedPath.startsWith(resolvedBaseDir + path.sep) && resolvedPath !== resolvedBaseDir) {\n\t\tthrow new ToolError(\"Path traversal is not allowed in skill:// URLs\");\n\t}\n\t// Agent Plugin skills (§4.1): the resource must canonically resolve within\n\t// the plugin root. Fail closed: a dangling or unresolvable path is rejected\n\t// rather than handed to bash, where writing through it could create the\n\t// outside target. Symlinks may target other files inside the same package.","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/bash-skill-urls.ts#L74-L110","documentation":"The path portion of the skill:// URL failed percent-decoding. decodeURIComponent throws on malformed escapes (a bare % not followed by two hex digits, or truncated sequences like %E4%B8), and the function converts that into this ToolError. The URL must use valid percent-encoding for its path.","triggerScenarios":"Calling resolveSkillUrlToPath with URLs such as \"skill://name/100%done.md\", \"skill://name/%ZZ\", or a path whose escape sequence is cut off. The error names the original URL.","commonSituations":"A model hand-writes percent signs in file names without encoding them (% as literal 'percent of'); double-encoding mangles sequences; a truncated URL from log/output clipping.","solutions":["Percent-encode the path properly: encode '%' as %25 and non-ASCII chars via encodeURIComponent.","Remove stray bare % characters from the path segment.","If the source was double-encoded, decode once at the producer instead of passing %%-style URLs."],"exampleFix":"// before\nresolveSkillUrlToPath(\"skill://my-skill/100%summary.md\", skills);\n\n// after\nconst p = encodeURIComponent(\"100%summary.md\");\nresolveSkillUrlToPath(`skill://my-skill/${p}`, skills);","handlingStrategy":"validation","validationCode":"function isWellEncoded(s: string): boolean {\n  try { decodeURIComponent(s); return true; } catch { return false; }\n}\n// check the raw path segment before calling the resolver","typeGuard":null,"tryCatchPattern":"try {\n  return resolveSkillUrlToPath(url, skills);\n} catch (e) {\n  if (e instanceof ToolError && e.message.startsWith(\"Invalid skill:// URL path encoding\")) {\n    // re-encode the path segment and retry once\n  } else throw e;\n}","preventionTips":["Always encodeURIComponent each path segment; never hand-place % in URLs.","Escape literal '%' as %25.","Avoid passing URLs through log/clipboard round-trips that may truncate them."],"tags":["url-parsing","encoding","skills"],"backgroundTag":"malformed-percent-encoding","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}