nexu-io/open-design · error
${field} must be a string
Error message
${field} must be a string What it means
optionalString (refresh.ts:512-516) is a typed accessor used by selectJsonPath (for input.path/file/name) and selectGithubRepositoryApiUrl (for input.url). It returns undefined when the field is absent but throws when the field is present and not a string, so callers cannot mistake a wrong-typed field for a missing one.
Source
Thrown at apps/daemon/src/live-artifacts/refresh.ts:514
const source = options.artifact.document.sourceJson;
const mapped = source.toolName === 'public_github_repository_metric' && source.outputMapping?.dataPaths !== undefined
? asBoundedRefreshOutput(applyDataPaths(options.documentOutput.output, source.outputMapping.dataPaths))
: applyLiveArtifactOutputMapping({
source,
output: options.documentOutput.output,
});
deepMergeBoundedJsonObject(dataJson, mapped);
if (source.toolName === 'public_github_repository_metric') {
applyLegacyGithubRepositoryMetricCompat(dataJson, options.documentOutput.output);
}
}
return { dataJson: asBoundedRefreshOutput(dataJson) };
}
function optionalString(value: BoundedJsonValue | undefined, field: string): string | undefined {
if (value === undefined) return undefined;
if (typeof value !== 'string') throw new Error(`${field} must be a string`);
return value;
}
function optionalPositiveInteger(value: BoundedJsonValue | undefined, field: string, defaultValue: number, maxValue: number): number {
if (value === undefined) return defaultValue;
if (!Number.isSafeInteger(value) || typeof value !== 'number' || value < 1) throw new Error(`${field} must be a positive integer`);
return Math.min(value, maxValue);
}
function selectJsonPath(input: ProjectFilesReadJsonInput): string {
const rawPath = optionalString(input.path, 'input.path') ?? optionalString(input.file, 'input.file') ?? optionalString(input.name, 'input.name');
if (rawPath === undefined) throw new Error('project_files.read_json requires input.path');
return validateProjectPath(rawPath);
}
function compactTextPreview(text: string, query: string | undefined): string {
const normalized = text.replace(/\s+/g, ' ').trim();
if (normalized.length <= 240) return normalized;View on GitHub (pinned to 5be4028344)
Solutions
- Pass the field as a string (e.g. input.path: 'data.json', input.url: 'https://...').
- Coerce known-safe values to string before constructing the input.
- Validate the input shape against the tool's input schema before refresh.
Example fix
// before
const input = { path: 42 };
// throws `input.path must be a string`
// after
const input = { path: 'data.json' }; Defensive patterns
Strategy: type-guard
Validate before calling
function optionalString(v: unknown, field: string): string | undefined {
if (v === undefined) return undefined;
if (typeof v !== 'string') throw new Error(`${field} must be a string`);
return v;
} Type guard
function isOptionalString(v: unknown): v is string | undefined {
return v === undefined || typeof v === 'string';
} Prevention
- Type the source.input against the tool's input schema (ProjectFilesReadJsonInput, PublicgithubRepositoryMetricInput).
- Coerce values to string at the boundary, or reject non-strings with a clear message.
- Run typecheck (`pnpm --filter @open-design/daemon typecheck`) after changing input shapes.
When it happens
Trigger: A refresh source.input provides path/file/name (for project_files.read_json) or url (for public_github_repository_metric) as a number, boolean, object, or array instead of a string.
Common situations: A connector/tool input built programmatically that passes a numeric path or a parsed-JSON object where a string was expected; a migration that changed a field's type; user input not coerced to string.
Related errors
- ${field} must be a positive integer
- connector refresh output must be a JSON object
- refresh source ${source.type} is not supported yet
- No refresh source is available yet.
- Refresh is disabled for this artifact source.
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/defe0bc1e41a22a7.
Report an issue: GitHub.