paperclipai/paperclip · error
must be a non-empty string.
Error message
${key} must be a non-empty string. What it means
parseConfig's text() helper validates each string config key: a present value must be a non-empty, non-whitespace string containing no NUL bytes. This error is thrown when a config key like apiUrl, apiKey, shape, rootfs, or region is provided but is not a usable string — for example a number, an empty string, whitespace, or a string containing "\0".
Solutions
- Inspect the raw config object and print the offending key's value and type.
- Coerce known-numeric values with String(value) before calling parseConfig, or fix the config source to emit strings.
- Trim or re-enter the value if it is empty/whitespace-only; provide the actual credential/URL.
- Re-upload the secret if it contains NUL or control bytes — it was likely corrupted.
Example fix
// before
const config = parseConfig({ apiUrl: process.env.CREATEOS_API_URL }); // undefined/empty env -> error
// after: validate and coerce first
const apiUrl = (process.env.CREATEOS_API_URL ?? "").trim();
if (!apiUrl) throw new Error("CREATEOS_API_URL is required");
const config = parseConfig({ apiUrl }); Defensive patterns
Strategy: validation
Validate before calling
function prevalidateConfig(raw) {
for (const key of ["apiUrl", "apiKey", "shape", "rootfs", "region"]) {
const v = raw[key];
if (v == null) continue;
if (typeof v !== "string" || !v.trim() || v.includes("\0"))
throw new Error(`${key} must be a non-empty string before calling parseConfig`);
}
} Type guard
function isNonEmptyString(v: unknown): v is string {
return typeof v === "string" && v.trim().length > 0 && !v.includes("\0");
} Try / catch
try {
config = parseConfig(raw);
} catch (err) {
if (err.message.includes("must be a non-empty string")) {
const key = err.message.split(" ")[0];
throw new Error(`Configuration field '${key}' is invalid; check the board plugin settings and secret source.`);
}
throw err;
} Prevention
- Validate config types at the boundary (env/JSON) before parseConfig.
- Never coerce numeric/boolean config values implicitly; convert explicitly with String().
- Re-enter secrets if they contain control characters — the source is likely corrupted.
- Add a startup config validation step that reports all invalid keys at once.
When it happens
Trigger: Setting a config value to "" or " ", passing a numeric/boolean value where a string is expected (e.g. region: 1), or supplying a value containing a NUL byte (often from decoding binary or corrupted secret storage).
Common situations: Environment variables interpolated to empty strings, secrets managers returning undefined coerced to "", YAML/JSON configs with wrong types, or pasted credentials containing invisible control characters.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- reuseLease must be a boolean.
- ACPX profile requires exact model ; received
- ACPX model must not be empty
- ACPX provider identity contains an invalid permission mode
- ACPX provider identity contains invalid lifetime fences
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/a9bed712e47207f5.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/config.ts:16
export interface CreateosConfig {
apiUrl: string;
apiKey: string | null;
shape: string;
rootfs: string | null;
region: string | null;
timeoutMs: number;
reuseLease: boolean;
}
export function parseConfig(raw: Record<string, unknown>): CreateosConfig {
const text = (key: string): string | null => {
const value = raw[key];
if (value == null) return null;
if (typeof value !== "string" || !value.trim() || value.includes("\0")) {
throw new Error(`${key} must be a non-empty string.`);
}
return value.trim();
};
const apiUrl = text("apiUrl");
if (!apiUrl) throw new Error("CreateOS requires an API URL.");
let url: URL;
try { url = new URL(apiUrl); } catch { throw new Error("CreateOS API URL is invalid."); }
// Configuration is board-owned, but never follow redirects with the API key.
// Plain HTTP is useful for a loopback development server only.
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
if ((url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) ||
url.username || url.password || url.search || url.hash ||
!["", "/", "/v1", "/v1/"].includes(url.pathname)) {
throw new Error("CreateOS API URL must be an HTTPS origin (optionally ending in /v1); HTTP is allowed on loopback only.");
}
const shape = text("shape");
if (!shape) throw new Error("CreateOS requires a shape from its shape catalog.");
const timeoutMs = raw.timeoutMs ?? 300_000;View on GitHub (pinned to 3f1d897a7c)