Skip to main content

Flow control directives

Flow control directives determine what happens after pattern matching. They provide fine-grained control over error handling and execution flow.

Available directives

DirectiveDescription
continueProceed to next step
skipSkip remaining steps in current iteration
abortStop mission with error
retryRetry previous request with backoff
queueSend to dead letter queue
jump...thenExecute action, then continue

Continue

Proceed to the next step normally:

match response {
{ data: _ } -> continue,
_ -> abort "No data"
}

// Execution continues here
store response.data -> data { key: .id }

Use continue when:

  • Pattern matches expected success case
  • You want explicit confirmation of flow

Skip

Skip remaining steps in the current loop iteration:

for item in items {
match item {
{ status: "inactive" } -> skip,
{ status: "deleted" } -> skip,
_ -> continue
}

// Only runs for active, non-deleted items
store item -> activeItems { key: .id }
}

Use skip when:

  • Item should be ignored but processing should continue
  • Filtering within a loop
  • Handling non-critical errors

Abort

Stop mission execution immediately:

match response {
{ error: msg } -> abort msg,
{ error: _ } -> abort "Unknown error occurred",
_ -> continue
}

With custom message:

match response {
{ code: 401 } -> abort "Authentication failed - check credentials",
{ code: 403 } -> abort "Permission denied - check API permissions",
{ code: 404 } -> abort "Resource not found",
{ code: 500 } -> abort "Server error - try again later",
_ -> continue
}

Use abort when:

  • Unrecoverable error occurs
  • Critical validation fails
  • Continuing would cause data corruption

Retry

Retry the previous HTTP request:

match response {
{ error: _, code: 429 } -> retry {
maxAttempts: 5,
backoff: exponential,
initialDelay: 1000,
maxDelay: 60000
},
_ -> continue
}

Retry options

OptionDescriptionDefault
maxAttemptsMaximum retry attempts3
backoffStrategy: exponential, linear, constantexponential
initialDelayFirst retry delay (ms)1000
maxDelayMaximum delay (ms)30000
delayFixed delay (overrides backoff)-

Simple retry

match response {
{ code: 503 } -> retry, // Uses defaults
_ -> continue
}

Fixed delay

match response {
{ code: 429, headers: h } -> retry {
delay: h["Retry-After"] * 1000
},
_ -> continue
}

Queue

Send failed items to a dead letter queue:

match response {
{ error: e } -> queue dlq {
item: {
request: currentRequest,
error: e,
timestamp: now()
}
},
_ -> continue
}

Queue options

queue queueName {
item: itemToStore,
key: optionalKey
}

Store the queue as a regular store:

mission ErrorHandling {
store dlq: file("dead-letter-queue")

action Process {
get "/data"

match response {
{ error: e } -> queue dlq { item: { error: e } },
_ -> continue
}
}
}

Jump then

Execute another action, then continue or retry:

match response {
{ code: 401 } -> jump RefreshToken then retry,
_ -> continue
}

action RefreshToken {
post "/auth/refresh" {
body: { refreshToken: env("REFRESH_TOKEN") }
}
}

Jump then retry

Common pattern for token refresh:

action FetchData {
get "/protected-resource"

match response {
{ code: 401 } -> jump RefreshToken then retry,
_ -> store response -> data { key: .id }
}
}

action RefreshToken {
post "/oauth/token" {
body: {
grant_type: "refresh_token",
refresh_token: env("REFRESH_TOKEN")
}
}
// New token is automatically used
}

Jump then continue

match response {
{ needsSetup: true } -> jump SetupResource then continue,
_ -> continue
}

Combining directives

Layered error handling

match response {
// Retry transient errors
{ code: 429 } -> retry { maxAttempts: 5 },
{ code: 503 } -> retry { maxAttempts: 3 },
{ code: 504 } -> retry { maxAttempts: 3 },

// Handle auth separately
{ code: 401 } -> jump RefreshToken then retry,

// Queue unrecoverable errors
{ code: 400, error: e } -> queue dlq { item: { error: e } },

// Abort on critical errors
{ code: 500, error: e } -> abort e,

// Skip missing resources
{ code: 404 } -> skip,

// Continue on success
_ -> continue
}

Per-item error handling

for item in items {
get concat("/items/", item.id)

match response {
{ error: _, code: 404 } -> {
// Log and skip
store { id: item.id, status: "not_found" } -> missing
skip
},
{ error: e } -> {
// Queue for retry
queue failed { item: { id: item.id, error: e } }
skip
},
_ -> continue
}

store response -> processedItems { key: .id }
}

Best practices

Be specific

// Good: specific error handling
match response {
{ code: 401 } -> jump RefreshToken then retry,
{ code: 403 } -> abort "Permission denied",
{ code: 429 } -> retry { delay: 60000 },
{ code: 404 } -> skip,
{ error: e } -> abort e,
_ -> continue
}

// Avoid: too generic
match response {
{ error: _ } -> retry,
_ -> continue
}

Always have a default

match response {
{ status: "ok" } -> continue,
{ status: "error" } -> abort "Error",
_ -> abort "Unexpected response" // Always have catch-all
}

Log before abort

match response {
{ error: e } -> {
store { error: e, timestamp: now() } -> errorLog
abort e
},
_ -> continue
}

Use queue for later processing

match response {
{ error: "rate_limit" } -> queue retryQueue {
item: {
request: currentRequest,
retryAfter: response.retryAfter
}
},
_ -> continue
}