{"record":{"id":"aa98e986b45673a3","repo":"can1357/oh-my-pi","slug":"invalid-suffix-contains-directory-separator","errorCode":null,"errorMessage":"invalid suffix {}, contains directory separator","messagePattern":"invalid suffix (.+?), contains directory separator","errorType":"error_code","errorClass":"MkTempError","httpStatus":null,"severity":"error","filePath":"crates/pi-builtins/src/mktemp.rs","lineNumber":66,"sourceCode":"const TMPDIR_ENV_VAR: &str = \"TMP\";\n\nconst FALLBACK_TMPDIR: &str = \"/tmp\";\n\n#[derive(Error, Debug)]\nenum MkTempError {\n\t#[error(\"could not persist file {}\", .0.quote())]\n\tPersist(PathBuf),\n\n\t#[error(\"with --suffix, template {} must end in X\", .0.quote())]\n\tMustEndInX(String),\n\n\t#[error(\"too few X's in template {}\", .0.quote())]\n\tTooFewXs(String),\n\n\t#[error(\"invalid template, {}, contains directory separator\", .0.quote())]\n\tPrefixContainsDirSeparator(String),\n\n\t#[error(\"invalid suffix {}, contains directory separator\", .0.quote())]\n\tSuffixContainsDirSeparator(String),\n\n\t#[error(\"invalid template, {}; with --tmpdir, it may not be absolute\", .0.quote())]\n\tInvalidTemplate(OsString),\n\n\t#[error(\"too many templates\")]\n\tTooManyTemplates,\n\n\t#[error(\"failed to create {} via template {}: No such file or directory\", .0, .1.quote())]\n\tNotFound(String, PathBuf),\n\n\t#[error(transparent)]\n\tIo(#[from] io::Error),\n}\n\n/// Options parsed from the command line.\n///\n/// This provides a layer of indirection between the application logic and","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-builtins/src/mktemp.rs#L48-L84","documentation":"MkTempError::SuffixContainsDirSeparator is raised when the value passed to --suffix contains a directory separator ('/'). The suffix is appended to the generated filename; a slash would imply a nested path that mktemp will not create, so it rejects the argument up front.","triggerScenarios":"`mktemp --suffix=dir/.tmp XXXXXX` or --suffix built from a path variable containing '/'; shell expansion injecting a path fragment into the suffix.","commonSituations":"Dynamically composed suffixes like `--suffix=\".$(basename $0).tmp\"` where basename unexpectedly returns a path; copy-pasted suffixes including directories; cross-platform scripts with path-shaped suffixes.","solutions":["Remove any '/' from the --suffix value; use only plain filename characters (e.g. .txt, .log)","Compute directory placement via --tmpdir instead of encoding it in the suffix","Sanitize dynamic suffixes: `suffix=$(basename \"$candidate\")` before use","If a nested name is needed, create the directory and pass it as --tmpdir"],"exampleFix":"// before\nmktemp --suffix=\"sub/x.log\" XXXXXX\n// after\nmktemp --tmpdir=\"./sub\" --suffix=.log XXXXXX","handlingStrategy":"validation","validationCode":"if (suffix.includes('/')) {\n  throw new RangeError(`invalid suffix '${suffix}': must not contain '/'`);\n}","typeGuard":"const isPlainSuffix = (s: string): boolean => !s.includes('/') && s.length > 0;","tryCatchPattern":"try {\n  await Bun.$`mktemp --suffix=${suffix} ${tmpl}`.quiet();\n} catch (e) {\n  if (String(e).includes('invalid suffix')) {\n    const clean = path.basename(suffix); // strip any path shape\n    await Bun.$`mktemp --suffix=${clean} ${tmpl}`.quiet();\n  } else throw e;\n}","preventionTips":["Run dynamic suffixes through basename before use","Keep suffixes to simple extension strings like .log/.tmp","Never embed directory structure in --suffix; use --tmpdir","Reject path-shaped suffixes in wrapper scripts"],"tags":["cli","path-validation","mktemp"],"backgroundTag":"invalid-template-argument","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}