{"record":{"id":"017b21cc700b5cb5","repo":"can1357/oh-my-pi","slug":"with-suffix-template-must-end-in-x","errorCode":null,"errorMessage":"with --suffix, template {} must end in X","messagePattern":"with --suffix, template (.+?) must end in X","errorType":"error_code","errorClass":"MkTempError","httpStatus":null,"severity":"error","filePath":"crates/pi-builtins/src/mktemp.rs","lineNumber":57,"sourceCode":"static OPT_TMPDIR: &str = \"tmpdir\";\nstatic OPT_P: &str = \"p\";\nstatic OPT_T: &str = \"t\";\n\nstatic ARG_TEMPLATE: &str = \"template\";\n\n#[cfg(not(windows))]\nconst TMPDIR_ENV_VAR: &str = \"TMPDIR\";\n#[cfg(windows)]\nconst 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())]","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-builtins/src/mktemp.rs#L39-L75","documentation":"MkTempError::MustEndInX is raised by the builtin `mktemp` when --suffix is supplied but the template does not end in X (after accounting for the suffix). With --suffix, GNU mktemp requires the X-run to terminate the template portion so it can correctly splice the generated random characters before the suffix. This prevents ambiguous or corrupted filenames.","triggerScenarios":"Calling `mktemp --suffix=.txt fooXXXXbar` where the template part doesn't end in X, or `mktemp --suffix=.log templateXXextra`.","commonSituations":"Moving from plain `mktemp fooXXXXXX` to suffixed form without realizing the X-run must end at the template/suffix boundary; building templates dynamically where the suffix got merged into the template string.","solutions":["End the template with X's right before the suffix: `mktemp --suffix=.txt XXXXXX`","Move trailing non-X characters out of the template into --suffix","Drop --suffix and embed the whole pattern manually if a non-X tail is truly needed (then the X-run must still be at the end)","Ensure no whitespace or hidden characters trail the X-run in the template"],"exampleFix":"// before\nmktemp --suffix=.log fooXXXXbar\n// after\nmktemp --suffix=.log fooXXXX","handlingStrategy":"validation","validationCode":"function checkSuffixTemplate(template: string, hasSuffix: boolean): void {\n  if (hasSuffix && !/X[^X]*$/.test(template) === false) {\n    // with --suffix the template portion must END in X\n  }\n  if (hasSuffix && !template.endsWith('X')) {\n    throw new RangeError(`with --suffix, template '${template}' must end in X`);\n  }\n}","typeGuard":"const endsInX = (t: string): boolean => t.endsWith('X');","tryCatchPattern":"try {\n  await Bun.$`mktemp --suffix=${suffix} ${tmpl}`.quiet();\n} catch (e) {\n  if (String(e).includes('must end in X')) {\n    // move trailing non-X chars into the suffix and retry\n    await Bun.$`mktemp --suffix=${tail + suffix} ${tmpl.replace(/[^X]+$/, '')}`.quiet();\n  } else throw e;\n}","preventionTips":["When using --suffix, always terminate the template with an X-run","Keep the X-run and suffix as separate variables, never concatenate first","Build templates via a helper that enforces the X-suffix boundary","Review templates after refactors that append extensions"],"tags":["cli","argument-validation","mktemp"],"backgroundTag":"invalid-template-argument","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}