koala73/worldmonitor · warning · ApiError
Aircraft identifier is too long
Error message
Aircraft identifier is too long
What it means
trackAircraft caps both req.icao24 and req.callsign at 16 raw characters before normalization; anything longer throws ApiError(400, 'Aircraft identifier is too long'). This is a cheap pre-normalization length guard so oversized garbage never reaches providers or rate-limit admission.
Solutions
- Limit the identifier input to 16 characters client-side before calling
- Send icao24 and callsign as separate fields, never combined
- Catch ApiError and surface 'Aircraft identifier is too long' with a hint to shorten input
Example fix
// before
await trackAircraft(ctx, { icao24: '406b7f' , callsign: 'DLH441 LH441 EXTRA' });
// after
const callsign = raw.callsign.slice(0, 8);
await trackAircraft(ctx, { icao24: raw.icao24.slice(0, 16), callsign }); Defensive patterns
Strategy: validation
Validate before calling
if (req.icao24.length > 16 || req.callsign.length > 16) throw new Error('aircraft identifiers must be <= 16 chars'); Type guard
const isShortIdentifier = (v: unknown): v is string => typeof v === 'string' && v.length <= 16;
Try / catch
try {
return await trackAircraft(ctx, req);
} catch (e) {
if (e instanceof ApiError && e.status === 400 && /too long/.test(e.message)) {
return { error: 'Identifier too long', fix: 'trim to <= 16 chars' };
}
throw e;
} Prevention
- Slice identifier inputs to 16 characters at the UI layer
- Keep icao24 and callsign in separate fields
- Sanitize pasted ADS-B text before lookup
When it happens
Trigger: Calling track-aircraft with icao24 or callsign strings longer than 16 characters, e.g. pasted full hex dumps, concatenated identifiers, or an unbounded user input field.
Common situations: UI search box sending 'icao24=abc123 callsign=UAL123' as one string; logging pipelines passing whole ADS-B messages; test fixtures with placeholder multi-field strings.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Webhook URL DNS resolution failed: ${message}
- Dynamic ApiError(400, message) via local invalid() helper (e
- Dynamic ApiError(400, message) via local invalid() helper (e
- Expected a six-character hexadecimal ICAO address
- Expected an alphanumeric callsign of at most eight character
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/dd17afa74515e52d.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/aviation/v1/track-aircraft.ts:118
if (req.icao24) return `aviation:track:icao:${req.icao24}:v2`;
if (req.callsign) return `aviation:track:callsign:${req.callsign.toUpperCase()}:v2`;
if (!isDegenerateBbox(req)) {
return `aviation:track:bbox:${Math.floor(req.swLat)}:${Math.floor(req.swLon)}:${Math.ceil(req.neLat)}:${Math.ceil(req.neLon)}:v1`;
}
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', '');
req = { ...req, icao24, callsign };
if (icao24 || callsign) await admitIdentifierLookup(ctx.request);
const redistributableOnly = requiresRedistributableProviders(ctx.request);
const cacheKey = `${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 () => {
const relayBase = getRelayBaseUrl();
const isCallsignOnly = !!req.callsign && !req.icao24 && isDegenerateBbox(req);View on GitHub (pinned to 7d06c8633d)