mifi/lossless-cut · error · UserFacingError
Expression did not lead to a string
Error message
Expression did not lead to a string
What it means
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.
Source
Thrown at src/renderer/src/util/outputNameTemplate.ts:219
[selectedSegNumVariable]: selectedSegNumPadded,
SEG_LABEL: segLabels.length === 1 ? segLabels[0] : segLabels,
EPOCH_MS: epochMs,
CUT_FROM: cutFromStr,
CUT_FROM_NUM: cutFrom,
CUT_TO: cutToStr,
CUT_TO_NUM: cutTo,
CUT_DURATION: cutDurationStr,
[segTagsVariable]: tags && {
// allow both original case and uppercase
...tags,
...Object.fromEntries(Object.entries(tags).map(([key, value]) => [`${key.toLocaleUpperCase('en-US')}`, value])),
},
FILE_EXPORT_COUNT: currentFileExportCount != null ? currentFileExportCount + 1 : undefined,
EXPORT_COUNT: exportCount != null ? exportCount + 1 : undefined,
} satisfies FileNameTemplateContext;
const ret = (await safeishEval(`\`${template}\``, context));
if (typeof ret !== 'string') throw new UserFacingError(i18n.t('Expression did not lead to a string'));
return ret;
}
function maybeTruncatePath(fileName: string, truncate: boolean) {
// Split the path by its separator, so we can check the actual file name (last path seg)
const pathSegs = fileName.split(pathSep);
if (pathSegs.length === 0) return '';
const [lastSeg] = pathSegs.slice(-1);
const rest = pathSegs.slice(0, -1);
return [
...rest,
// If sanitation is enabled, make sure filename (last seg of the path) is not too long
truncate ? lastSeg!.slice(0, maxFileNameLength) : lastSeg,
].join(pathSep);
}
async function generateWithFallback({ generate, desiredTemplate, defaultTemplate, safeOutputFileName, filePath, outputDir, maxLabelLength }: {View on GitHub (pinned to 3b9a59c288)
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.
Example fix
// before (template user typed): ${CUT_FROM_NUM}
// after (template): cut-${CUT_FROM_NUM} Defensive patterns
Strategy: validation
Validate before calling
// Validate that the template evaluates to a string before relying on it
export async function evalTemplateToString(template: string, ctx: object): Promise<string> {
const ret = await safeishEval(`\`${template}\``, ctx);
if (typeof ret !== 'string') {
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().`);
}
return ret;
} Type guard
const evaluatesToString = (v: unknown): v is string => typeof v === 'string';
Try / catch
try {
fileName = await evalFileNameTemplate(template, context);
} catch (err) {
if (err instanceof UserFacingError && /did not lead to a string/.test(err.message)) {
showError('Filename template produced a non-string value. Combine it with literal text.');
fileName = 'output';
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- No rows found
- Invalid start or end value. Must contain a number of seconds
- Unsupported file
- Smart cut is not possible when FPS is unknown
- Cannot find any keyframe after the desired start cut point
AI-assisted analysis of mifi/lossless-cut@3b9a59c288 (2026-08-12).
Data as JSON: /api/errors/1d65d5f985b38778.
Report an issue: GitHub.