Error Handling
When to Use
Use this skill when the user asks about error handling, checking return codes, debugging ovstage failures, or troubleshooting an operation that failed at enqueue, wait, or fetch.
Inputs
Resolve inputs in this order: existing repository files and referenced snippets, explicit user request, then broader agent context.
- Failure point: a synchronous call returning
ovstage_api_status_t, an async enqueue returningovstage_enqueue_result_t, anovstage_wait_op, or afetch_*call. - Error signal available: the returned
ovstage_api_status_tcode, an enqueuestatus, anop_id, or theovstage_op_wait_result_tfrom a wait. - Operation being debugged and whether the caller needs recovery, logging, or propagation guidance.
- Repository source snippets referenced below. Treat these snippets as the API source of truth.
Prerequisites
- Use an ovstage checkout that contains the referenced example/snippets.
- Read the relevant
> **Source:**snippet before writing or explaining API usage. - Distinguish enqueue success from execution success: an enqueue returning
OVSTAGE_OKmeans the op was accepted, not that it completed. Completion/errors surface atovstage_wait_op(or the matchingfetch_*).
Instructions
- Identify the failure point: synchronous
ovstage_api_status_t, async enqueue, wait, or fetch. - For synchronous calls, compare the returned
ovstage_api_status_tagainstOVSTAGE_OK; on failure callovstage_get_error_string(instance, code)for a human-readable message. - For async enqueues, check
ovstage_enqueue_result_t.status == OVSTAGE_OKbefore usingop_index. A non-OK status leavesop_index == OVSTAGE_INVALID_OP_ID. - After
ovstage_wait_op, inspectovstage_op_wait_result_t.error_op_ids/error_op_id_count; for each failed id callovstage_get_last_op_error(instance, op_id)immediately (the strings are thread-local and invalidated by the next wait on the same thread). - Release op-tracking state with
ovstage_release_op(instance, op_id)once the op is known complete. - When changing code, run the narrow ovstage unit test that exercises the failing operation whenever practical.
For USD export operations, wait with the matching export-specific wait to obtain
its report. That wait does not consume unrelated generic population failures;
observe and drain those separately with the generic population wait. Route
destination ownership and save/rollback questions to exporting-to-usd.
Output Format
- For explanations, cite the relevant API names, source snippets, and caveats.
- For code changes, summarize the files changed, snippets affected, and validation run.
Scripts
This skill has no scripts.
Limitations
- The referenced snippets remain the source of truth; update or add tested snippets before documenting new API usage.
- Logging is separate from error handling. Error reporting is via the return codes
and error-string accessors below. For diagnostic logging — a process-global callback
with a severity threshold and RUST_LOG-style channel filter (
ovstage_set_log_callback/ovstage_flush_log) — see theloggingskill; it is a distinct surface, not an error-reporting mechanism. - Diagnostics.
ovstage_get_error_string(instance, code)(returns a staticconst char*, never NULL) andovstage_get_last_op_error(instance, op_id)(returns anovx_string_t,{NULL, 0}if the op id is unknown / did not fail) are vtable-dispatched (take theinstance).ovstage_get_last_error(void)is a free function (no instance) returning the most recent thread-local synchronous error as anovx_string_t— readable even whenovstage_create_instanceitself failed. - Python: covered in the Python section — failures surface as
OvstageError/OvxErrorexceptions (bothRuntimeError); async errors raise from.wait(). Inline Python pending a shipping Python example to snippet-source. - Snippets are sourced from the shipping example
examples/c/minimal/main.cpp.
Overview
Every synchronous ovstage call returns an ovstage_api_status_t (a typed enum of status codes).
OVSTAGE_OK (0) is success; any non-zero value is an error. Asynchronous enqueue calls
return an ovstage_enqueue_result_t { status; op_index; }; completion and per-op errors are
observed later via ovstage_wait_op. Convert any code to text with
ovstage_get_error_string(instance, code) (returns a static string, never NULL). The
most recent thread-local error is also available as an ovx_string_t via
ovstage_get_last_error().
Error codes
| Code | Meaning |
|---|---|
OVSTAGE_OK (0) |
success |
OVSTAGE_ERROR_INVALID_ARGUMENT (1) |
NULL pointer or invalid parameter |
OVSTAGE_ERROR_INVALID_HANDLE (2) |
stale/invalid handle |
OVSTAGE_ERROR_NOT_FOUND (3) |
token/path/list not found |
OVSTAGE_ERROR_PRIM_NOT_FOUND (4) |
INSERT mode and prim already exists |
OVSTAGE_ERROR_WRITE_FLOOR_VIOLATION (5) |
write at/below the floor, or latest state / a selected in-range change above it |
OVSTAGE_ERROR_NOT_SUPPORTED (6) |
unsupported operation |
OVSTAGE_ERROR_QUEUE_FULL (7) |
backpressure: submit queue full |
OVSTAGE_ERROR_END_OF_ITERATION (8) |
no more groups to iterate |
OVSTAGE_ERROR_OUT_OF_MEMORY (9) |
allocation failure |
OVSTAGE_ERROR_LAYOUT_CHANGED (10) |
layout changed during map |
OVSTAGE_ERROR_TIMEOUT (11) |
fetch/wait did not complete within timeout |
OVSTAGE_ERROR_OP_FAILED (12) |
an enqueued op failed; see ovstage_get_last_op_error |
OVSTAGE_ERROR_OUT_OF_RANGE (13) |
requested ordinal range cannot be materialized from retained payloads |
OVSTAGE_ERROR_INTERNAL (99) |
internal error |
C — synchronous calls
Compare the returned code against OVSTAGE_OK; stringify failures with
ovstage_get_error_string:
Source:
examples/c/minimal/main.cppsnippetcheck-sync-error
ovstage_get_error_string maps any status code to a human-readable string (never NULL,
distinct per code); ovstage_get_version reports the runtime version. The public
support-API test asserts both:
Source:
tests/c/test_support_api.cppsnippetversion-and-error-c
A concrete synchronous rejection: an INSERT write whose target prim already exists is
refused by the synchronous preflight with OVSTAGE_ERROR_PRIM_NOT_FOUND (before anything is
written). The C enqueue returns that status; the Python Operation.wait() raises it. The
public write-modes tests assert this admission rule against UPSERT:
Source:
tests/c/test_write_modes.cppsnippetupsert-vs-insert-c
Source:
tests/python/test_write_modes.pysnippetupsert-vs-insert
C — async enqueue + wait
Check the enqueue status before using op_index, wait on the op, then inspect per-op
errors from the wait result:
Source:
examples/c/minimal/main.cppsnippetenqueue-wait-error
OVSTAGE_ERROR_TIMEOUT from ovstage_wait_op is not a failure of the op — it means the
op (or a dependency) has not completed yet; ovstage_op_wait_result_t.lowest_pending_op_id
reports the lowest still-pending id in the waited op's dependency chain.
A concrete asserted failure: an op that runs and fails surfaces through error_op_ids
with the wait itself still returning OVSTAGE_OK, and the reason lives in the per-op
error string. The public test drives a clone onto an already-existing target and checks
both the failure and the "already exists" reason:
Source:
tests/c/test_error_handling.cppsnippetclone-target-exists-error-c
Python
The Python bindings turn return-code failures into exceptions (both subclass
RuntimeError, carrying the numeric code + message):
ovstage.OvstageError— raised byStageoperations (wrapsovstage_get_error_string/ovstage_get_last_op_error).ovstage.OvxError— raised byPathDictionaryoperations.
Synchronous calls raise on failure. For async ops, the enqueue returns a handle object
(Query / Read / Map) whose .wait(timeout) raises OvstageError if the op (or a
dependency) failed — the equivalent of inspecting error_op_ids in C.
Source:
examples/python/minimal/main.pysnippeterror-handling
An op that ran and failed raises OvstageError from .wait() with the op-level code
ErrorCode.OP_FAILED and the reason in .message. The public test asserts this on a
clone onto an already-existing target:
Source:
tests/python/test_error_handling.pysnippetclone-target-exists-error
A timeout surfaces as the OVSTAGE_ERROR_TIMEOUT code (timeout=0 polls,
OVSTAGE_TIMEOUT_INFINITE blocks, other finite values wait up to the timeout).
Key Types / Functions
| Symbol | Role |
|---|---|
ovstage_api_status_t |
typed enum return/status code; OVSTAGE_OK = success |
ovstage_get_error_string(instance, code) |
code → static const char* (never NULL) |
ovstage_get_last_error() |
free function; most recent thread-local error → ovx_string_t |
ovstage_enqueue_result_t { status; op_index; } |
result of an async enqueue |
ovstage_wait_op(instance, op_id, timeout, &wait_result) |
wait for an op + its deps |
ovstage_op_wait_result_t { error_op_ids; error_op_id_count; lowest_pending_op_id; } |
per-wait error/progress data |
ovstage_get_last_op_error(instance, op_id) |
failed-op id → ovx_string_t ({NULL, 0} if unknown), transient (thread-local) |
ovstage_release_op(instance, op_id) |
release op-tracking state after completion |
Troubleshooting
- An enqueue returning
OVSTAGE_OKonly means accepted. Always wait (or fetch) and checkerror_op_idsbefore trusting the op's effect. ovstage_get_last_op_error(instance, op_id)and theerror_op_idsarray are transient thread-local memory, invalidated by the nextovstage_wait_opon the same thread. Copy the string if you need to keep it. (See thestring-handlingskill for copyingovx_string_t.)ovstage_wait_opwaits for all ops up to and includingop_idwithin its dependency chain, not just that single op.- Treat
OVSTAGE_ERROR_TIMEOUTas "not ready yet," distinct fromOVSTAGE_ERROR_OP_FAILED("the op ran and failed"). OVSTAGE_ERROR_WRITE_FLOOR_VIOLATIONmeans a write or delete targeted an ordinal at or below the effective write floor, a latest read targets current recorded state above it, or an explicit range selects an in-range change above it. The effective floor starts at 0, so advance it to cover positive-ordinal state before reading.advance_write_flooritself never raises this: backwards advances clamp viamax(...)rather than rejecting.OVSTAGE_ERROR_OUT_OF_RANGEon an explicit range means a selected(attribute, path)also has a retained change after the range end, so the payload for that fixed range is no longer available. Widen/rebase the range or request current state. Sealing the later change alone does not resolve this error.- Call
ovstage_release_oponce an op is known complete; afterwards theop_idmust not be reused withovstage_wait_oporovstage_get_last_op_error.
References
- Use the
> **Source:**directives in this skill to locate tested snippets before reusing API patterns. string-handlingskill —ovx_string_thandling (includingovstage_get_last_op_error/ovstage_get_last_error;ovstage_get_error_stringremains staticconst char*).cpu-ahead-gpu-asyncskill — op-id / wait lifecycle and async completion semantics.- Keep related skills, docs, and snippets synchronized when changing the workflow.