koala73/worldmonitor · error · ValidationError
Valid phone number is required
Error message
Valid phone number is required
What it means
Phone format gate (submit-contact.ts:167): the trimmed value must match PHONE_RE = /^[+(]?\d[\d\s()./-]{4,23}\d$/. Optional leading '+' or '(', then a digit, then 4-23 characters of digits/space/parens/dots/slashes/dashes, ending with a digit — total length 6-25, letters anywhere fail. The input is trimmed but not normalized, so stored formatting is preserved.
Source
Thrown at server/worldmonitor/leads/v1/submit-contact.ts:168
const { email, name, organization, phone, message, source } = req;
if (!email || !EMAIL_RE.test(email)) {
throw new ValidationError([{ field: 'email', description: 'Invalid email' }]);
}
const emailDomain = email.split('@')[1]?.toLowerCase();
if (emailDomain && FREE_EMAIL_DOMAINS.has(emailDomain)) {
throw new ApiError(422, 'Please use your work email address', '');
}
if (!name || name.trim().length === 0) {
throw new ValidationError([{ field: 'name', description: 'Name is required' }]);
}
if (!organization || organization.trim().length === 0) {
throw new ValidationError([{ field: 'organization', description: 'Company is required' }]);
}
if (!phone || !PHONE_RE.test(phone.trim())) {
throw new ValidationError([{ field: 'phone', description: 'Valid phone number is required' }]);
}
const safeName = name.slice(0, MAX_FIELD);
const safeOrg = organization.slice(0, MAX_FIELD);
const safePhone = phone.trim().slice(0, 30);
const safeMsg = message ? message.slice(0, MAX_MESSAGE) : undefined;
const safeSource = source ? source.slice(0, 100) : 'enterprise-contact';
const convexUrl = process.env.CONVEX_URL;
if (!convexUrl) {
throw new ApiError(503, 'Service unavailable', '');
}
const client = new ConvexHttpClient(convexUrl);
try {
await client.mutation(api.contactMessages.submit, {
name: safeName,
email: email.trim(),View on GitHub (pinned to a96956387a)
Solutions
- Strip non-dialable characters server- or client-side and keep 6-25 chars starting with optional '+'
- Move extensions to a separate field
- Validate with the exact same regex client-side before submit: /^[+(]?\d[\d\s()./-]{4,23}\d$/.test(phone.trim())
- Prefer E.164-style input (+12125551234) which always matches
Example fix
// before
await submitContact({ phone: '(+44) 20 7946 0000', ... }); // '(' then '+' -> ValidationError
// after
await submitContact({ phone: '+44 20 7946 0000', ... }); // matches PHONE_RE Defensive patterns
Strategy: validation
Validate before calling
const PHONE_RE = /^[+(]?\d[\d\s()./-]{4,23}\d$/;
const phone = rawPhone.trim();
if (!phone || !PHONE_RE.test(phone)) {
showFieldError('phone', 'Enter a valid phone number (6-25 chars, digits and + ( ) . / - only, no extensions)');
return;
}
await submitContact({ ...form, phone }); Type guard
function isContactPhone(v: unknown): v is string {
return typeof v === 'string' && /^[+(]?\d[\d\s()./-]{4,23}\d$/.test(v.trim());
} Try / catch
try { await submitContact(form); }
catch (e) {
if (isValidationError(e)) {
for (const v of e.violations ?? []) if (v.field === 'phone') showFieldError('phone', v.description);
return;
}
throw e;
} Prevention
- Capture extensions in a separate field
- Prefer E.164 (+12125551234), which always matches
- Apply the exact server regex client-side before the captcha step
When it happens
Trigger: Extensions or labels containing letters ("+1 555 123 4567 ext. 9"); a string shorter than 6 chars ("12345"); trailing punctuation ("555-1234-"); '(+44) 20 7946 0000' — after '(' the next char must be a digit, so '+' fails; values longer than 25 chars; empty phone.
Common situations: International formats with '(' plus '+' combined; users pasting numbers with extension notes; UIs that capture phone as free text without a mask; edge cases like '00 44 20 7946 0000' which actually pass (leading 0 is a digit) while '(+...' does not.
Related errors
- Invalid email address
- Invalid email
- Name is required
- Company is required
- get_intel_timeline requires at least one of domain ("conflic
AI-assisted analysis of koala73/worldmonitor@a96956387a (2026-08-21).
Data as JSON: /api/errors/7916bbba6bfcc065.
Report an issue: GitHub.