koala73/worldmonitor · warning · ApiError
Expected a six-character hexadecimal ICAO address
Error message
Expected a six-character hexadecimal ICAO address
What it means
trackAircraft requires that a provided icao24, once trimmed and lowercased, matches /^[0-9a-f]{6}$/ — exactly six hexadecimal characters. Empty is allowed (then callsign must be supplied); any other shape throws ApiError(400, 'Expected a six-character hexadecimal ICAO address').
Solutions
- Send exactly six hex characters, e.g. '4ca2b6'
- Strip separators and validate with /^[0-9a-fA-F]{6}$/ before calling
- If you only have a callsign, omit icao24 entirely and pass callsign
Example fix
// before
await trackAircraft(ctx, { icao24: '4C-A2-B6' });
// after
const icao24 = raw.replace(/[^0-9a-fA-F]/g, '').toLowerCase();
await trackAircraft(ctx, { icao24 }); Defensive patterns
Strategy: validation
Validate before calling
if (icao24 && !/^[0-9a-fA-F]{6}$/.test(icao24.trim())) throw new Error('icao24 must be 6 hex characters'); Type guard
const isIcao24 = (v: unknown): v is string => typeof v === 'string' && /^[0-9a-fA-F]{6}$/.test(v.trim()); Try / catch
try {
return await trackAircraft(ctx, { icao24 });
} catch (e) {
if (e instanceof ApiError && e.status === 400) {
return { error: e.message, hint: 'expected six hex chars, e.g. 4ca2b6' };
}
throw e;
} Prevention
- Strip dashes/spaces from copied ICAO24 values
- Lowercase before sending to match server normalization
- Fall back to callsign-only lookup when you lack a valid ICAO24
When it happens
Trigger: Calling track-aircraft with icao24 like 'ABC123' (uppercase is fine only because it is lowercased first, but 'G' letters outside a-f are not), 5- or 7-character hex, or identifiers containing separators ('40-6B-7F').
Common situations: Passing registration numbers ('D-ABCD') or flight numbers as icao24; copying ICAO24 with spaces/dashes from ADS-B sites; truncating a longer hex string by hand.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- Expected an alphanumeric callsign of at most eight…
- Expected three-letter IATA airport codes
- Aircraft identifier is too long
- Custom scorecard bloc members must be uppercase ISO-2 codes.
- Dynamic ApiError(400, message) via local invalid() helper…
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22).
Data as JSON: /api/errors/3c64dc036abacb2d.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/aviation/v1/track-aircraft.ts:122
if (req.icao24) return `aviation:track:icao:${req.icao24}:v2`;
if (req.callsign) return `aviation:track:callsign:${req.callsign.toUpperCase()}:v2`;
return 'aviation:track:all:v2';
}
// Response-level source values (TrackAircraftResponse.source):
// 'opensky' — data from OpenSky via relay
// 'wingbits' — data from Wingbits via relay
// 'none' — all real sources returned empty or failed; positions = []
export async function trackAircraft(
ctx: ServerContext,
req: TrackAircraftRequest,
): Promise<TrackAircraftResponse> {
const rawIcao24 = req.icao24 ?? '';
const rawCallsign = req.callsign ?? '';
if (rawIcao24.length > 16 || rawCallsign.length > 16) throw new ApiError(400, 'Aircraft identifier is too long', '');
const icao24 = rawIcao24.trim().toLowerCase();
const callsign = rawCallsign.trim().toUpperCase();
if (rawIcao24 && !/^[0-9a-f]{6}$/.test(icao24)) throw new ApiError(400, 'Expected a six-character hexadecimal ICAO address', '');
if (rawCallsign && !/^[A-Z0-9]{1,8}$/.test(callsign)) throw new ApiError(400, 'Expected an alphanumeric callsign of at most eight characters', '');
if (![req.swLat, req.swLon, req.neLat, req.neLon].every(Number.isFinite)) throw new ApiError(400, 'Expected finite viewport coordinates', '');
const lat1 = Math.max(-90, Math.min(90, req.swLat));
const lat2 = Math.max(-90, Math.min(90, req.neLat));
const lon1 = Math.max(-180, Math.min(180, req.swLon));
const lon2 = Math.max(-180, Math.min(180, req.neLon));
req = { ...req, icao24, callsign, swLat: Math.min(lat1, lat2), neLat: Math.max(lat1, lat2), swLon: Math.min(lon1, lon2), neLon: Math.max(lon1, lon2) };
if (icao24 || callsign) await admitIdentifierLookup(ctx.request);
const redistributableOnly = requiresRedistributableProviders(ctx.request);
const cacheKey = `${await buildCacheKey(req)}${redistributableOnly ? ':redistributable' : ''}`;
let result: { positions: PositionSample[]; source: string } | null = null;
try {
const positiveTtl = req.callsign ? CALLSIGN_CACHE_TTL : CACHE_TTL;
const negativeTtl = req.callsign ? CALLSIGN_NEGATIVE_TTL : CACHE_TTL;
result = await cachedFetchJson<{ positions: PositionSample[]; source: string }>(
cacheKey, positiveTtl, async () => {View on GitHub (pinned to e586b8b4b8)