{"record":{"id":"1d65d5f985b38778","repo":"mifi/lossless-cut","slug":"expression-did-not-lead-to-a-string","errorCode":null,"errorMessage":"Expression did not lead to a string","messagePattern":"Expression did not lead to a string","errorType":"exception","errorClass":"UserFacingError","httpStatus":null,"severity":"error","filePath":"src/renderer/src/util/outputNameTemplate.ts","lineNumber":219,"sourceCode":"    [selectedSegNumVariable]: selectedSegNumPadded,\n    SEG_LABEL: segLabels.length === 1 ? segLabels[0] : segLabels,\n    EPOCH_MS: epochMs,\n    CUT_FROM: cutFromStr,\n    CUT_FROM_NUM: cutFrom,\n    CUT_TO: cutToStr,\n    CUT_TO_NUM: cutTo,\n    CUT_DURATION: cutDurationStr,\n    [segTagsVariable]: tags && {\n      // allow both original case and uppercase\n      ...tags,\n      ...Object.fromEntries(Object.entries(tags).map(([key, value]) => [`${key.toLocaleUpperCase('en-US')}`, value])),\n    },\n    FILE_EXPORT_COUNT: currentFileExportCount != null ? currentFileExportCount + 1 : undefined,\n    EXPORT_COUNT: exportCount != null ? exportCount + 1 : undefined,\n  } satisfies FileNameTemplateContext;\n\n  const ret = (await safeishEval(`\\`${template}\\``, context));\n  if (typeof ret !== 'string') throw new UserFacingError(i18n.t('Expression did not lead to a string'));\n  return ret;\n}\n\nfunction maybeTruncatePath(fileName: string, truncate: boolean) {\n  // Split the path by its separator, so we can check the actual file name (last path seg)\n  const pathSegs = fileName.split(pathSep);\n  if (pathSegs.length === 0) return '';\n  const [lastSeg] = pathSegs.slice(-1);\n  const rest = pathSegs.slice(0, -1);\n\n  return [\n    ...rest,\n    // If sanitation is enabled, make sure filename (last seg of the path) is not too long\n    truncate ? lastSeg!.slice(0, maxFileNameLength) : lastSeg,\n  ].join(pathSep);\n}\n\nasync function generateWithFallback({ generate, desiredTemplate, defaultTemplate, safeOutputFileName, filePath, outputDir, maxLabelLength }: {","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/mifi/lossless-cut/blob/3b9a59c288bf6e11076b583c932cfa48ddab3b02/src/renderer/src/util/outputNameTemplate.ts#L201-L237","documentation":"Thrown by the output-filename template evaluator in outputNameTemplate.ts when safeishEval() of the user's template literal returns a value that is not a string. The template is interpolated inside a JS template string with the FileNameTemplateContext, and if the entire expression evaluates to a non-string (e.g. a number, object, array, or undefined) the result cannot be used as a filename. It is a post-eval type guard.","triggerScenarios":"A filename template whose top-level expression is a bare variable holding a non-string (e.g. '${CUT_FROM_NUM}' yielding a number, or '${SEG_LABEL}' yielding an array when multiple labels exist); a template that resolves to undefined because every variable used was optional and unset; a template that returns an object literal.","commonSituations":"User writes a template referencing a numeric context variable without coercion (e.g. just '${EPOCH_MS}'); SEG_LABEL evaluates to an array when more than one segment label is present; a template referencing an undefined variable that produces undefined; arithmetic in the template yielding a non-string.","solutions":["Wrap the whole template expression so it always yields a string, e.g. prefix/suffix with literal text or use String(): '${String(CUT_FROM_NUM)}'.","Avoid using a bare numeric/array variable as the entire template; combine it with static text.","For SEG_LABEL, ensure a single label is selected or coerce: '${Array.isArray(SEG_LABEL) ? SEG_LABEL.join('-') : SEG_LABEL}'.","Open the output filename template editor and test the template against the current segment to see the evaluated value."],"exampleFix":"// before (template user typed): ${CUT_FROM_NUM}\n\n// after (template): cut-${CUT_FROM_NUM}","handlingStrategy":"validation","validationCode":"// Validate that the template evaluates to a string before relying on it\nexport async function evalTemplateToString(template: string, ctx: object): Promise<string> {\n  const ret = await safeishEval(`\\`${template}\\``, ctx);\n  if (typeof ret !== 'string') {\n    throw new Error(`Template did not produce a string (got ${ret === undefined ? 'undefined' : Array.isArray(ret) ? 'array' : typeof ret}). Add static text or coerce with String().`);\n  }\n  return ret;\n}","typeGuard":"const evaluatesToString = (v: unknown): v is string => typeof v === 'string';","tryCatchPattern":"try {\n  fileName = await evalFileNameTemplate(template, context);\n} catch (err) {\n  if (err instanceof UserFacingError && /did not lead to a string/.test(err.message)) {\n    showError('Filename template produced a non-string value. Combine it with literal text.');\n    fileName = 'output';\n  }\n}","preventionTips":["Always combine bare variables with literal text in the template (e.g. 'out-${EPOCH_MS}').","Coerce numeric/array context values with String() or .join('-') inside the template.","Test the template against the current segment in the editor before batch export.","Avoid using SEG_LABEL alone when multiple labels may be present."],"tags":["template","export","filename","eval","user-input","types"],"backgroundTag":null,"analyzedSha":"3b9a59c288bf6e11076b583c932cfa48ddab3b02","analyzedAt":"2026-08-12T20:54:25.651Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}