# Debug Operation

> Triage a failing Usd Optimize operation. Use when an op errors, silently no-ops, or returns unexpected output.

- Skill: `nvidia-omniverse-usd-optimize/debug-operation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nvidia-omniverse-usd-optimize/debug-operation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nvidia-omniverse-usd-optimize/debug-operation/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: NVIDIA-omniverse (https://skillmd.com/u/nvidia-omniverse-usd-optimize)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/nvidia-omniverse-usd-optimize/debug-operation

---


<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# Debug Operation

Structured workflow for triaging an operation that fails, produces unexpected
output, or silently does nothing.

## What this skill covers

Search this doc for keywords like `verbose`, `analysisMode`, `argument key`,
`silent no-op`, `mesh-less`, `instanced`, `libusd` to jump.

- **Step 1** — reproduce and capture the log.
- **Step 2** — check the basics (argument keys, input stage).
- **Step 3** — enable verbose + analysis mode.
- **Step 4** — diagnose by failure pattern (tables for common failure modes).
- **Operation-family cheat sheet** — per-family gotchas.

Companion skills: `run-operations` (how to invoke), `tune-parameters`
(when the op runs but the output isn't right), `build` (when the build
itself is broken).

---

## Step 1 — Reproduce and capture the log

Run the operation through the `usdOptimize` CLI with full logging, and omit
`-w` so no (bad) output file is written.

```bash
# POSIX — verbose, capture stats, no output written
BIN=_build/linux-x86_64/release/bin/usdOptimize
"$BIN" -i "$ASSET" -o <key> -a <arg>=<val> -v -s > "$TMPDIR/debug-run.log" 2>&1
```

`-v` is verbose and `-s` captures before/after stats. Omitting `-w` means
nothing is written. The log captures the C++ `[INFO]`/`[WARN]`/`[ERROR]`
lines. For a multi-op chain, use `-c <config.json>` instead of `-o`/`-a`.

**Do not add `-r` here.** `-r` diverts the operation's warnings into the
report file instead of stdout, so `[WARNING]` lines the checks below grep
for never reach your log. If you also want the report, run a second pass
with `-r` added rather than folding it into this one.

If the user already has a log (e.g. from a prior `run-operations` run),
skip this step and read their log directly.

## Step 2 — Check the basics

Most "operation does nothing" reports fall into one of three categories.
Check them in order before going deeper.

### 2a. Argument key mismatch

An unknown key in a `-c` config logs `[WARNING] Unknown argument '<key>' for
operation '<op>' -- ignoring it` and the operation runs with the default, so
grep the log for `Unknown argument` first. (Via `-a` it's a hard error instead:
`Error: invalid argument specified`, exit 1.) Verify every key in the user's
config matches the operation's `addArgument()` declarations:

```bash
# Check the C++ source for the canonical argument keys
rg 'addArgument' source/operations/<key>/ --glob '*.cpp' --glob '*.h'
```

Or check the argument list in `docs/operations/<key>.rst`. Common mistakes:

- Camel-case vs snake-case (`mergeVertices` not `merge_vertices`).
- Plural vs singular (`paths` not `path`, `meshPrimPaths` not `meshPrimPath`).
- Boolean as string (`true` not `"true"` in JSON).

### 2b. Wrong operation key

The operation key is the first string argument to the `Operation(...)` base
constructor, not the class name or the `USD_OPTIMIZE_PLUGIN_INIT` argument. Verify:

```bash
rg 'Operation\(' source/operations/<key>/ --glob '*.cpp' -A1 | head -5
```

### 2c. Input stage has no matching prims

Many operations scope work via a `paths` argument or operate only on
specific prim types. If the stage has no prims matching the filter, the
operation returns success but does nothing.

```python
# Quick check: does the stage have meshes?
from pxr import Usd, UsdGeom
stage = Usd.Stage.Open("<asset>")
meshes = [p for p in stage.TraverseAll() if p.IsA(UsdGeom.Mesh)]
print(f"{len(meshes)} meshes")
```

## Step 3 — Enable verbose and analysis mode

If the basics check out, run in analysis mode to see what the operation
*would* do without mutating the stage:

```python
from usd_optimize.core import ExecutionContext, UsdOptimizeCore
from pxr import Usd, UsdUtils

stage = Usd.Stage.Open("<asset>")
context = ExecutionContext()
context.usdStageId = UsdUtils.StageCache.Get().Insert(stage).ToLongInt()
context.analysisMode = 1
context.verbose = 1

success, error, output = UsdOptimizeCore.getInstance().executeOperation(
    "<key>", context, {<args>}
)

print("success:", success)
print("error:", error)
print("output:", output)
```

Not all operations support analysis mode. Check:

```bash
rg 'getSupportsAnalysis' source/operations/<key>/ --glob '*.cpp'
```

If the operation doesn't override `getSupportsAnalysis`, analysis mode is
unavailable — fall back to running on a disposable copy of the stage.

For the full CLI flag reference (`-v`, `-s`, `-an`, `-r`, `-st`), see
`docs/cli.rst`.

## Step 4 — Diagnose by failure pattern

### Hard failures (operation returns error)

| Log pattern | Likely cause | Fix |
|---|---|---|
| `Stage not found` or `Invalid stage id` | `ExecutionContext.usdStageId` not set or stale. | Use `context.set_stage(stage)` or set `usdStageId` via `UsdUtils.StageCache`. |
| `Operation '<key>' not found` | Operation plugin didn't load — either not built or not on the plugin search path. | Rebuild (`./repo.sh build`). Check `UsdOptimizeCore.getInstance().getOperations()` for the registered list. |
| `libusd` mismatch / stage-cache miss (see `Operation.cpp` error) | Two copies of `libusd` loaded — Python's `pxr` resolves to a different one than the C++ core. | Use the build's bundled Python via the wrapper scripts. See `run-validators` § CLI invocation for the `libusd` alignment exports. |
| Crash / segfault during execution | Usually a real bug. | Isolate the failing prim path, write a minimal repro, and file against the operation. |

### Silent no-ops (operation returns success, nothing changed)

| Pattern | Likely cause | Fix |
|---|---|---|
| Log says `0 meshes processed` or similar | `paths` filter doesn't match any prims, or stage has no matching prim types. | Check the `paths` argument. An empty `paths` array means "all prims" for most ops, but some interpret it as "nothing selected." |
| Operation ran but output is identical to input | Arguments are at defaults that produce no change (e.g. `reductionFactor: 100` keeps everything). | Review the parameter defaults in the guide or C++ source. |
| Log shows processing but the save failed | `--no-save` was passed, or `stage.GetRootLayer().Save()` / `.Export()` wasn't called. | Check the runner output for the save step. |

### Unexpected output (operation ran but result is wrong)

| Pattern | Likely cause | Fix |
|---|---|---|
| Only some prims were affected | Operation scoped by `paths` or a prim-type filter. | Widen `paths` or check `meshPrimPaths` / similar scope arguments. |
| Values look wrong (e.g. extreme decimation) | World-unit mismatch — parameter values are in stage units, and `metersPerUnit` doesn't match assumptions. | Check `UsdGeom.GetStageMetersPerUnit(stage)` and scale parameters accordingly. |
| Merge produced unexpected grouping | `mergePoint` / `rootPath` / `considerMaterials` settings. | See `docs/operations/merge.rst`. |

---

## Operation-family cheat sheet

### Mesh operations (`meshCleanup`, `decimateMeshes`, `merge`, etc.)

- Require `UsdGeomMesh` prims. No meshes = no work.
- `merge` won't merge a boundary with a single mesh unless
  `allowSingleMeshes: true`.
- `decimateMeshes` can't reduce below the topological minimum per mesh.
  Assets with many small parts will overshoot the target percentage.
- `reductionFactor` is a percentage (0–100), not a fraction. `0.5` means
  "keep 0.5%", not "keep 50%".

### Deduplication (`deduplicateGeometry`, `deduplicateHierarchies`)

- Prims with existing references/payloads are excluded from grouping.
- Instanced prims can't be authored in place — deinstance first.
- `deduplicateHierarchies` traverses from the stage's default prim.
  No default prim = no work.
- Material-related prims (`Material`, `Shader`, `NodeGraph`, `Looks`,
  `Materials` scopes) are intentionally skipped.

### Analysis-only operations (`findCoincidingGeometry`, `findFlatHierarchies`, `findOverlappingMeshes`, `findOccludedMeshes`)

- These detect but don't fix. The "fix" is a separate operation
  (e.g. `removePrims`, `flattenHierarchy`).
- Results come back in `output["analysis"]` — inspect that dict.

### Material operations (`optimizeMaterials`)

- `convertToColor: true` replaces material networks with flat colors.
  Unintended if the user didn't ask for it.

### Hierarchy operations (`flattenHierarchy`, `pruneLeaves`)

- These operate on the prim hierarchy, so they run on mesh-less stages too.
- `flattenHierarchy` has params that control depth; defaults may not
  flatten enough.

---

## See also

- `docs/operations/<key>.rst` — per-operation parameter reference and tuning guidance.
- `docs/cli.rst` — CLI flags (`-v`, `-s`, `-an`, `-r`, ...).
- `.agents/skills/run-operations/SKILL.md` — errors table for driver failures.
- `.agents/skills/tune-parameters/SKILL.md` — when the operation runs but
  output quality needs iteration.

## Purpose

Provide a structured triage workflow for an Usd Optimize operation that fails,
silently no-ops, or produces unexpected output. Unifies log reading,
argument verification, verbose / analysis-mode invocation, and a
per-family cheat-sheet of common failure patterns so the agent can move
from a vague "it didn't work" report to a concrete root cause without
guessing.

## Prerequisites

- A built repo (`./repo.sh build` or `repo.bat build`) — verbose /
  analysis-mode invocation requires the bundled Python.
- The operation key (e.g. `meshCleanup`) and the user's invocation —
  config JSON, command line, or Python snippet — so the actual
  arguments are known.
- A reproducible invocation. If the failure is intermittent, capture a
  log first with `--verbose --captureStats --no-save`.

## Limitations

- This skill does not patch C++ source — its output is a diagnosis, not
  a code fix. For changes to the operation itself, the user works in
  `source/operations/<key>/` directly.
- Analysis mode is unavailable for operations that don't override
  `getSupportsAnalysis()`. The skill falls back to running on a
  disposable stage copy in those cases.
- Crashes / segfaults are surfaced as bug reports — the skill won't
  attach a debugger or symbolicate stacks.

## Troubleshooting

This whole skill *is* a troubleshooting guide for Usd Optimize operations. The
table below covers the meta-failure modes — when this skill itself
can't make progress.

| Symptom | Likely cause | Fix |
|---|---|---|
| Verbose log is empty | `executionContext` step missing or placed after the failing op. | Put `{"operation":"executionContext","verbose":true,"captureStats":true}` as the first config entry. |
| Analysis mode reports `not supported` | Operation doesn't implement `executeAnalysisImpl`. | Drop analysis mode; run on a copy of the stage with `--no-save`. |
| Op succeeds standalone but fails in chain | Earlier step in the chain mutated the stage in a way that invalidates the inputs. | Bisect the chain — run the failing op directly after the input is loaded. |
| `libusd` mismatch error from `Operation.cpp` | Two `libusd` builds loaded — typical with system `pxr` + dev-tree Usd Optimize. | Use the build's bundled Python via the wrapper scripts (`tools/perf_*/run.sh`). See `run-validators` § CLI invocation for the `libusd` alignment exports. |


