Error Reporting & Catching

Three severity levels govern failure in OcaltQL: WARNING, ERROR, and FATAL. Each behaves differently, and only one of them is catchable.

Levels

Level Meaning Catchable
WARNINGThe statement succeeded, but the pattern used is risky or approaching a limitNo — execution continues, last_ok is set false
ERRORThe statement failed due to an external condition — the script logic is sound, the environment didn’t cooperateYes — via OR CATCH ERROR
FATALThe script itself is structurally invalid, or a loop reached the iteration/time ceiling (INFINITE)No — terminates immediately, always

CATCH — SET form

CATCH ERROR SET ?var is used exactly like any other OR fallback — it only runs if the preceding statement’s last_ok was false, and it captures the structured error object instead of simply falling through.

Example
FILE READ "/root/missing.txt" SET ?data OR CATCH ERROR SET ?err
AFTER IF ?err IS NOT NULL
OPEN
  EMIT "Failed: " & ?err("message") & " (" & ?err("code") & ")"
CLOSE

Error object fields: message, level, code, verb

Database error
INSERT INTO DB "shopdb" TABLE "nonexistent" ROW "x" AS 1 OR CATCH ERROR SET ?err
AFTER EMIT ?err("verb") & " failed: " & ?err("message")

CATCH — OPEN / CLOSE form

The OPEN ... CLOSE form allows any operation to run as the error handler, not just a single SET. Inside the block, !ERROR('key') gives access to the error object fields as a global, the same way !SESSION or !POST work.

Multi-step handler
FILE READ "/root/missing.txt" SET ?data
OR CATCH ERROR
OPEN
  EMIT "Read failed: " & !ERROR('message')
  AFTER MAIL TEXT BODY "File read failed: " & !ERROR('message') TO "admin@ocalt.com" SUBJECT "Alert"
  AFTER REMEMBER "last_error" AS !ERROR('code')
CLOSE

Use SET ?var when the error object needs to persist or be passed elsewhere. Use OPEN ... CLOSE when the handler itself is a multi-step operation.

WAIT FOR and timeouts

Explicit timeout — catchable
FETCH "https://slow-api.com/data" SET PROMISE ?job
AFTER WAIT FOR ?job 5 SECONDS SET ?result OR CATCH ERROR SET ?err
AFTER EMIT ?err("message")

If WAIT FOR is called with no explicit timeout, the runtime caps it at 300 seconds internally. If nothing resolves in that time, it escalates to FATAL rather than ERROR — an unbounded wait is treated as a script-authoring failure, not an environmental one.

No timeout — implicit 300s cap, FATAL on expiry
FETCH "https://api.example.com" SET PROMISE ?job
AFTER WAIT FOR ?job SET ?result
(* No timeout specified — capped at 300 seconds. If unresolved by then,
   this is FATAL and cannot be caught. *)

WARNING triggers

Unscoped update
UPDATE ROWS FROM DB "shopdb" TABLE "users" SET "status" AS "banned"
(* No WHERE — every row updated. Syntactically legal, WARNING triggered. *)

FATAL triggers

Illegal multivariable indexing
STRING "alpha" SET ?arr AND STRING "beta" SET ?arr
AFTER SET ?arr(0) AS "gamma"
(* ?arr is a Multi — indexing into it without COLLAPSE first is FATAL *)

FATAL cannot be caught by CATCH under any circumstances, at any nesting depth. It always terminates the script.