koala73/worldmonitor · error · ConvexError
COMPANY_MONITORING_MODEL_VERSION_INVALID
COMPANY_MONITORING_MODEL_VERSION_INVALID
Error message
COMPANY_MONITORING_MODEL_VERSION_INVALID
What it means
Thrown by admissionModelVersion() (convex/companyMonitoring/admission.ts:38) for both the modelVersion and requestedModelVersion arguments of the classification admission mutation. A version string is rejected when it differs from its own trim() (leading/trailing whitespace) or fails /^[^\u0000-\u001f\u007f]{1,200}$/u — i.e. empty, longer than 200 characters, or containing control characters.
Solutions
- Trim the version and validate it against the same regex before invoking the mutation: value === value.trim() && /^[^\u0000-\u001f\u007f]{1,200}$/u.test(value)
- Fail fast at worker startup when the model version env var is missing or empty, so the mutation never sees ""
- Cap the version string to a short logical tag (e.g. "gpt-4o-2024-08-06") instead of full build metadata
- Add a preflight assertion in the worker config loader that runs the server regex on every deploy
Example fix
// before
const modelVersion = process.env.MODEL_VERSION; // may be "gpt-4o\n" or ""
await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, { modelVersion, ... });
// after
const MODEL_VERSION_RE = /^[^\u0000-\u001f\u007f]{1,200}$/u;
function admissionModelVersion(value: string): string {
const trimmed = value.trim();
if (value !== trimmed || !MODEL_VERSION_RE.test(trimmed)) {
throw new Error(`MODEL_VERSION invalid: ${JSON.stringify(value)}`);
}
return trimmed;
}
await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, {
modelVersion: admissionModelVersion(process.env.MODEL_VERSION ?? ""),
...
}); Defensive patterns
Strategy: validation
Validate before calling
const MODEL_VERSION_RE = /^[^\u0000-\u001f\u007f]{1,200}$/u;
function assertModelVersion(value: string): string {
const trimmed = value.trim();
if (value !== trimmed || !MODEL_VERSION_RE.test(trimmed)) {
throw new Error(`model version invalid: ${JSON.stringify(value)}`);
}
return trimmed;
}
const modelVersion = assertModelVersion(process.env.MODEL_VERSION ?? "");
const requestedModelVersion = assertModelVersion(config.requestedModelVersion); Type guard
function isAdmissionModelVersion(value: unknown): value is string {
return (
typeof value === "string" &&
value === value.trim() &&
/^[^\u0000-\u001f\u007f]{1,200}$/u.test(value)
);
} Try / catch
try {
await ctx.runMutation(internal.companyMonitoring.admission.recordClassification, args);
} catch (err) {
if (err instanceof ConvexError && err.data === "COMPANY_MONITORING_MODEL_VERSION_INVALID") {
failFastAtStartup(`MODEL_VERSION malformed: ${JSON.stringify(args.modelVersion)}`);
}
throw err;
} Prevention
- Trim and validate the model version once at worker startup, not per request
- Fail startup when the version env var is missing or empty
- Use short logical version tags, not full build fingerprints
- Run the server regex against the version in CI for every deploy
When it happens
Trigger: Passing a model version read from an env var or config file that carries a trailing \n; passing "" because the env var was never set and the default is empty; a version string like "claude-3-5-sonnet\t20241022"; an extremely long version built from git describe plus build metadata exceeding 200 chars.
Common situations: MODEL_VERSION sourced from dotenv/CI secrets that silently include newlines; a deploy that changes version format (semver to full build fingerprint) pushing length past 200; copy-pasting a version from a release page with a non-breaking space or control character; upgrade scripts that leave the variable unset.
Related errors
- COMPANY_MONITORING_EVIDENCE_REVISION_INVALID
- COMPANY_MONITORING_ _INVALID
- userId is required
- COMPANY_MONITORING_ADMISSION_EVIDENCE_INVALID
- COMPANY_MONITORING_ADMISSION_EVIDENCE_MISSING
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/ea7c3d2be42ad69e.
Report an issue: GitHub.
Appendix: source
Thrown at convex/companyMonitoring/admission.ts:38
} from "./admissionSnapshot";
const ADMISSION_LEASE_MS = 5 * 60 * 1000;
const ADMISSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
const ADMISSION_MODEL_VERSION = /^[^\u0000-\u001f\u007f]{1,200}$/u;
function admissionIdentifier(value: string, field: string) {
if (!ADMISSION_ID.test(value)) {
throw new ConvexError(`COMPANY_MONITORING_${field}_INVALID`);
}
return value;
}
function admissionModelVersion(value: string) {
if (
value !== value.trim() ||
!ADMISSION_MODEL_VERSION.test(value)
) {
throw new ConvexError("COMPANY_MONITORING_MODEL_VERSION_INVALID");
}
return value;
}
function canonicalValue(value: unknown): unknown {
if (Array.isArray(value)) return value.map(canonicalValue);
if (value && typeof value === "object") {
const row = value as Record<string, unknown>;
return Object.fromEntries(
Object.keys(row).sort().map((key) => [key, canonicalValue(row[key])]),
);
}
return value;
}
function admissionTerminalReason(decision: string) {
if (decision === "publish") return "admitted" as const;
if (decision === "reject") return "rejected" as const;View on GitHub (pinned to eeab0a219f)