Context
processJob in src/queue/job-dispatch.ts is the top-level Cloudflare Queues job dispatcher: a switch (message.type) (line 69) that fans out to dozens of handlers, ending at line 347 with no default: case.
If a message.type doesn't match any case — a stale message left over from a job type that was renamed/removed, a producer/consumer version skew during a rolling deploy, or a corrupted payload — processJob returns silently with zero logging. The caller in src/index.ts (queue() handler, around lines 86-87) then does:
await processJob(env, message.body);
message.ack();
so the message is permanently dropped with no observability at all — no log line, no metric, no audit event.
This is inconsistent with the codebase's own established pattern for "a job type is no longer relevant": src/index.ts already has an explicit, logged retired_review_job_ignored path for hosted-review-execution jobs that are deliberately retired (around line 60):
console.warn(JSON.stringify({ level: "warn", event: "retired_review_job_ignored", messageId: message.id, jobType: message.body.type }));
message.ack();
and the DLQ consumer (src/queue/dlq.ts, processDlqBatch) logs every dropped job via a structured dlq_message_dead_lettered event before acking. An unmatched message.type reaching processJob is the same class of "this job is being silently discarded" event, but today it is the only ack-and-drop path in the queue pipeline with no log line at all.
Requirements
- Add a
default: case to processJob's switch (message.type) in src/queue/job-dispatch.ts.
- The default case must log a structured event (mirroring the existing
retired_review_job_ignored / dlq_message_dead_lettered JSON log shape: level, event, and the job type) so an operator can see in logs/log-search that an unrecognized job type was silently dropped.
- Use
console.warn (matching the retired_review_job_ignored precedent for a non-fatal-but-noteworthy drop), not console.error (reserved for queue_message_failed-class failures elsewhere in src/index.ts).
- The default case must not throw —
processJob must keep returning normally so the existing ack-after-processJob flow in src/index.ts/src/server.ts is unaffected; this is purely an observability addition, not a behavior change to message handling.
- No change to any of the existing matched
case branches.
Deliverables
Test Coverage Requirements
Aim for 99%+ Codecov patch coverage on the touched lines in src/queue/job-dispatch.ts (src/** is in coverage.include). The new default: branch needs a dedicated test — grepping test/unit/ for processJob plus "unknown"/"default" currently returns no hits, so this is genuinely uncovered today.
Expected Outcome
An unrecognized JobMessage["type"] reaching processJob (from a rolling-deploy version skew, a stale queued message, or a corrupted payload) is now logged with a structured, greppable event before being acked, instead of vanishing with zero trace — matching the observability already given to every other "job intentionally not processed" path in this pipeline (retired_review_job_ignored, dlq_message_dead_lettered).
Links & Resources
src/queue/job-dispatch.ts (processJob, switch (message.type) starting line 69, ending line 347, no default:)
src/index.ts (retired_review_job_ignored precedent around line 60; the queue() handler's await processJob(env, message.body); message.ack(); call around lines 86-87)
src/queue/dlq.ts (processDlqBatch's dlq_message_dead_lettered structured-log precedent)
Context
processJobinsrc/queue/job-dispatch.tsis the top-level Cloudflare Queues job dispatcher: aswitch (message.type)(line 69) that fans out to dozens of handlers, ending at line 347 with nodefault:case.If a
message.typedoesn't match anycase— a stale message left over from a job type that was renamed/removed, a producer/consumer version skew during a rolling deploy, or a corrupted payload —processJobreturns silently with zero logging. The caller insrc/index.ts(queue()handler, around lines 86-87) then does:so the message is permanently dropped with no observability at all — no log line, no metric, no audit event.
This is inconsistent with the codebase's own established pattern for "a job type is no longer relevant":
src/index.tsalready has an explicit, loggedretired_review_job_ignoredpath for hosted-review-execution jobs that are deliberately retired (around line 60):and the DLQ consumer (
src/queue/dlq.ts,processDlqBatch) logs every dropped job via a structureddlq_message_dead_letteredevent before acking. An unmatchedmessage.typereachingprocessJobis the same class of "this job is being silently discarded" event, but today it is the only ack-and-drop path in the queue pipeline with no log line at all.Requirements
default:case toprocessJob'sswitch (message.type)insrc/queue/job-dispatch.ts.retired_review_job_ignored/dlq_message_dead_letteredJSON log shape:level,event, and the job type) so an operator can see in logs/log-search that an unrecognized job type was silently dropped.console.warn(matching theretired_review_job_ignoredprecedent for a non-fatal-but-noteworthy drop), notconsole.error(reserved forqueue_message_failed-class failures elsewhere insrc/index.ts).processJobmust keep returning normally so the existing ack-after-processJobflow insrc/index.ts/src/server.tsis unaffected; this is purely an observability addition, not a behavior change to message handling.casebranches.Deliverables
default:case added toprocessJob's switch statement insrc/queue/job-dispatch.tsthat logs anunknown_job_type_ignored-style structured event (job type + any available message metadata) and returns normally.processJobwith an unrecognizedmessage.typelogs the new event and does not throw.Test Coverage Requirements
Aim for 99%+ Codecov patch coverage on the touched lines in
src/queue/job-dispatch.ts(src/**is incoverage.include). The newdefault:branch needs a dedicated test — greppingtest/unit/forprocessJobplus"unknown"/"default"currently returns no hits, so this is genuinely uncovered today.Expected Outcome
An unrecognized
JobMessage["type"]reachingprocessJob(from a rolling-deploy version skew, a stale queued message, or a corrupted payload) is now logged with a structured, greppable event before being acked, instead of vanishing with zero trace — matching the observability already given to every other "job intentionally not processed" path in this pipeline (retired_review_job_ignored,dlq_message_dead_lettered).Links & Resources
src/queue/job-dispatch.ts(processJob,switch (message.type)starting line 69, ending line 347, nodefault:)src/index.ts(retired_review_job_ignoredprecedent around line 60; thequeue()handler'sawait processJob(env, message.body); message.ack();call around lines 86-87)src/queue/dlq.ts(processDlqBatch'sdlq_message_dead_letteredstructured-log precedent)