**This PR is the 0.7.19 release** (`scripts/release/bump-version.sh
patch`): merging it runs Publish Release. Its receiver changes under
`apps/api` deploy on the same merge through Deploy API, minutes before
PyPI has 0.7.19, so schema 4 is read before any client sends it.
Fixes for what PostHog's first day of telemetry showed (2026-10-08
00:14Z to about 21:40Z: about 209 installs and 59 crash reports). It
covers three bugs people are hitting, crash reports that were not
cadgen's bugs, and gaps in what the receiver lets us see. There is one
commit per fix.
## Bugs
**1. Builds that export a mesh crashed on Windows** (7 installs, all
Windows, about 26 crashes). `mesh_export.py` ran the Node exporter with
`text=True` and no encoding, so Windows read its UTF-8 output in the
local code page. The exporter's JSON report names every output path, so
any output folder whose name the code page cannot read (for example
`Рабочий стол` under cp1252, or most Chinese text under cp936) made
CPython's Windows output reader die quietly. `proc.stdout` came back
`None`, and `.splitlines()` raised an `AttributeError`. The exporter now
reads `utf-8` with `errors="replace"`, which keeps the JSON line intact.
The same fix goes into `run_node_builder`, whose input was also silently
empty under cp1252. ffmpeg, `gz sdf` and `doctor` now read `utf-8` with
`errors="backslashreplace"`, and doctor's child process is set to
`PYTHONIOENCODING=utf-8`. The tests force subprocess's default encoding
to cp1252, and both fail without the fix.
**2. `cad_file` failed on 48 of 49 calls on Windows** (5 of 6 installs).
Codex for Windows names a file opened from its file tree as
`openai/resource.path = "/C:/Users/…"`, read from the desktop bundle.
Python 3.13's `ntpath.isabs("/C:/…")` is False, so every call answered
"not an absolute path". The `file.resourceUri` alongside it is a
`codex-resource://` handle, so the fallback never helped. A new
`local_path` drops the slash before a drive on Windows, both for file
URIs and for plain paths, for `cad_file`, `cad_open` and `cad_show`.
This most likely also explains Antigravity's `cad_show` failures on
Windows (7 of 12). The Windows CI job now passes the path the way Codex
spells it.
**3. `cad_screenshot` failed on 30% of calls** (11 of 19 installs). The
most likely cause is an agent capturing straight after build, show or
open, while the view is still loading or has not synced yet. The view
refused with "Wait for the displayed model revision to finish loading",
"That viewer is not open" or "No CAD viewer with a model is open", or a
large model ran past the fixed 10 s wait.
- The page now waits until the view shows the requested model, loaded
and drawn (`CAPTURE_SETTLE_MS`, 20 s).
- The server waits for a view it just opened to sync (`OPENING_SECONDS`,
15 s) within one budget for the whole capture (`CAPTURE_SECONDS`, 40 s).
- The capture's reply still goes on its own call (`void answer(event)`),
so no view call is held open.
## Crash reports that were not cadgen's bugs
- **Windows viewer disconnects.** `ConnectionAbortedError` (WinError
10053) made up most of the crash volume: 23 installs. The viewer caught
only `BrokenPipeError` and `ConnectionResetError`, and the header write
had no guard. Every write to the socket now treats any `ConnectionError`
as the page having left.
- **A model's own mistakes.** A build123d name that does not exist,
raised through the `cadgen.build123d` re-export, and a non-string passed
to `srgb()`. Both now raise deliberately, so the existing rule counts
them as the person's error, and `srgb` raises a `TypeError` naming what
it was given.
- **Stopped workers.** A worker stopped by SIGTERM, SIGINT or SIGHUP (a
person quitting it, a logout) now counts as cancelled, not crashed.
SIGSEGV, SIGABRT and SIGKILL are still reported.
## Telemetry: what we can now see
- **Why a tool call failed.** There is a new `tool_failure {tool,
reason, count}` event in batch schema 4, which PostHog receives as
`tool_failed`. The reason is one word from a fixed list (`no_path`,
`relative_path`, `no_file`, `not_cad`, `no_view`, `wrong_view`,
`bad_request`, `timeout`, `view_error`, `too_large`, `no_viewer`, `bug`,
`other`), chosen where the call fails and never taken from a message. A
test checks that every `ToolFailed` and `NoAnswer` names one.
- **Rollout: the receiver goes first.** The API is its own Vercel
project now (#587) and deploys on merge to `main`, so merging this PR
puts the schema 4 receiver live before any release sends schema 4. A
refused batch is dropped, as before; there is no fallback in the client.
- **Refused batches are logged.** Each 400, 403 or 415 is one
`console.warn` line naming the rule that failed and the cadgen version.
Values, install ids and service messages are never logged. Vercel's
per-status counts need Observability Plus, so this is the only way to
see a refusal. The privacy policy says so.
- **Errors are logged by name**, for example `TimeoutError` instead of
`23`. A `/v1/forget` timed out at 17:02Z, and the client retries it.
- **`$session_id`** is now set, so error tracking can count sessions.
Our ids are UUIDv4, so PostHog's sessions table leaves them out; error
tracking should still read them, which needs checking after deploy.
Privacy policy, README and `apps/api/README.md` are updated where what
is sent or logged changed.
## Not in this PR
- **Deduplicating a resent batch.** The sender rebuilds a failed window
instead of resending it, and a batch has no id, so there is nothing
stable to dedupe on yet. It needs a per-batch id from the sender.
- **Dashboard totals.** PostHog's error-tracking "occurrences" counts
events, not each event's `count`; for the mesh-export crash that is 5
against 22. That is fixed on the dashboard side (t2c-analytics).
- **5 of 15 DXF builds failed.** DXF builds don't go through Node, so
the encoding fix doesn't cover them and they still need a look.
## Needs a real host
- Windows Codex: open a `.step` from the file tree; capture from a tab
hidden behind another tab.
- Claude Desktop: capture right after `cad_show` on a large STEP, or
while the card waits on Allow.
- Antigravity on Windows: confirm the path spelling it sends.
## Tests
Full suites on this branch, in a provisioned worktree (`.venv` from
`requirements-dev.txt`, `npm ci`, `bundle.sh --check`,
`CADGEN_DAEMON=0`): all pass.
- `scripts/test/test-python.sh --keep-going`: 2,774 tests in 8 groups,
OK.
- `scripts/test/test-js.sh`: every group passes (core, ui, web, mcp).
- `scripts/test/test-docs.sh`: receiver tests 30/30 and the rest 16/16.
- `scripts/test/test-global.sh`: 210 tests, OK (1 skipped).
Each new regression test was run against the old code, and each fails
there.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
11 KiB
name, description, license
| name | description | license |
|---|---|---|
| srdf | MoveIt2 SRDF authoring, validation, and planning-semantics workflow. Use when creating, editing, inspecting, or validating `.srdf` files, MoveIt planning groups, virtual joints, passive joints, end effectors, group states, disabled collisions, URDF-paired planning semantics, or SRDF handoff for live review. Use the URDF skill for robot structure and the SDF skill for simulator descriptions. Open and visually review existing SRDF files in CAD Viewer. | MIT |
SRDF
Provenance: maintained in earthtojake/text-to-cad. Use the installed local skill files as the runtime source of truth; the repository link is only for provenance and release review.
Use this skill for MoveIt semantic robot descriptions on top of an existing valid URDF. SRDF defines planning semantics; it does not define physical robot structure. The .srdf file is the source of truth: author and edit the XML directly. There is no gen_srdf() contract.
SRDF correctness is a planning semantics problem. The common failure is not invalid XML; it is a plausible SRDF that gives MoveIt the wrong planning group, wrong tool link, wrong default state, unsafe disabled-collision matrix, or wrong joint units. Because language models are weak at spatial and kinematic reasoning, derive planning groups, end effectors, group states, and disabled collisions from the URDF topology, MoveIt Setup Assistant output, sampled collision analysis, or explicit user data. Do not infer them from visual theme alone — and do not type any link or joint name from memory: extract the URDF's link/joint table first and copy names from it.
Setup
Run cadgen through uv, so this skill's commands share one installation, and its warm build daemon, with the CAD app's server:
cadgenbelow meansuvx --no-config --managed-python --python 3.13 --from cadgen==0.7.19 cadgenpythonbelow meansuvx --no-config --managed-python --python 3.13 --from cadgen==0.7.19 python
The first run downloads that installation and the first snapshot its headless browser; later runs reuse both.
Format boundary
- URDF owns physical robot structure: links, joints, geometry, inertials, limits, mimic joints, transmissions, and robot-state publishing.
- SRDF owns MoveIt semantics: virtual joints, passive joints, planning groups, group states, end effectors, and disabled collision pairs.
- SDF owns simulator/world semantics: physics, sensors, lights, plugins, worlds, and simulation-specific metadata.
Do not place geometry, inertials, joint origins, link poses, mesh references, physical joint limits, transmissions, or ros2_control interfaces in SRDF.
Show the model
Show the user each file you create or change, and any they ask to see. Snapshots and validation don't replace this.
-
If your tools include
cad_show(your host may prefix it), use it with the file's absolute path, and follow its description for when to call it again.cad_viewreads what the user selected;cad_screenshotshows you what they see. Neither is a review of your own work. -
Otherwise run the CAD Viewer, from any folder:
cadgen viewer --host 127.0.0.1 --json --detach--detachreturns once the viewer answers requests and leaves it running in the background: always pass it, since a foreground viewer never exits (and piping its output throughtailcan hide the URL for good). It starts this machine's one viewer, or reuses it. Readurlfrom its one JSON line (never guess the port), and for each file returnurl?file=<its URL-encoded absolute path>. If it fails to launch, say so.
Keep the SRDF beside its uniquely matching URDF (same robot name). Review planning groups, named states and joints; visual review does not prove planning correctness.
Required workflow
- Start from a valid URDF. Author or fix the URDF first with
$urdfand validate it. The SRDF pairs with that URDF by colocation and robot name, and every name in the SRDF must exist in it. - Extract the URDF table. Before writing any SRDF XML, list the URDF's robot name, links, joints (with type, parent, child, limits, mimic flags). Copy names from this table only; never type them from memory. See
references/srdf-workflow.md. - Identify the planning task. Record whether the goal is arm IK, gripper control, mobile base planning, dual-arm planning, tool use, or local smoke testing.
- Create or update the planning ledger. Use
references/planning-ledger.mdbefore writing XML; keep a compact copy as a comment block in the.srdf. - Pair with the URDF by colocation. Save the
.srdfin the same folder as its.urdf, with the same<robot name>— that is the only linking mechanism. The validator and the viewer both resolve the pairing by scanning the folder for the URDF whose robot name matches; exactly one URDF per robot name per folder. No metadata element links the files. Seereferences/authoring-contract.md. - Define virtual and passive joints deliberately. Use them when needed by the robot model.
- Define planning groups from URDF topology. Prefer chain groups for serial manipulators when base/tip form a real parent-to-child path in the URDF tree (the validator verifies this). Use joint/link/subgroup definitions only when they are deliberate.
- Define end effectors after group membership is known. Avoid overlap between an end-effector group and its parent group. Record the actual target/TCP link.
- Define group states in URDF-native units. Revolute and continuous values are radians; prismatic values are meters. Do not store degrees in SRDF. Values must lie within URDF limits and must not set fixed or mimic joints.
- Generate disabled collisions from evidence. Use adjacency derived from the URDF joint table, MoveIt Setup Assistant sampling, or explicit user-provided collision matrices. Do not invent broad disable lists. See
references/disabled-collisions.md. - Validate every created or modified
.srdfwithcadgen srdf validate; it cross-validates all names, chains, states, and pairs against the paired URDF. Fix findings and re-validate until clean. - Run MoveIt smoke tests when available. Use MoveIt Setup Assistant or a project MoveIt launch directly.
- Report assumptions and skipped checks. Include incomplete validation, missing MoveIt environment, manually reasoned collision disables, and inferred target links.
Commands
Run cadgen as Setup defines it. cadgen doctor <skill-dir> reports the installation in use and checks that it is the one this skill pins — docs drift silently on another. Validation itself needs nothing beyond the Python standard library; only snapshots need the browser. Use cadgen <verb> --help for the complete current interface.
The validator shape is:
cadgen srdf validate path/to/robot.srdf
cadgen srdf validate path/to/robot.srdf --strict
cadgen srdf validate path/to/robot.srdf --json
The validator parses the SRDF, resolves the paired URDF (the same-folder .urdf whose robot name matches; none, several, or an invalid one is an error), and cross-validates: group/joint/link/subgroup name existence, chain path resolvability, subgroup cycles, virtual/passive joints, end-effector topology, group-state membership/limits/completeness, disabled-collision pairs (including Adjacent-reason truthfulness), and misspelled elements. Each phase collects all its findings in one pass (severity, code, XML path), but a structural error stops the cross-file phase — re-run after every fix. One run validates ONE file: --strict treats warnings as failures and --json prints one line of {"ok", "path", "issues": [{"severity", "code", "message", "element", "hint"}], "summary"}, where element is the XML path. It exits nonzero if the target fails. Relative targets resolve from the current working directory.
Hard rules
- The SRDF lives in the same folder as its URDF and shares its
<robot name>; that colocation-plus-name match is the only pairing mechanism, and exactly one URDF per robot name may exist in the folder. - Every link, joint, group, and subgroup name must come from the URDF table or a group defined in the same file.
- Group states use URDF-native units: radians for revolute/continuous, meters for prismatic.
- Disabled collision pairs require truthful reasons and provenance.
- End-effector groups should not share links with their parent planning group.
- Visual rendering review is useful but cannot prove planning correctness.
Snapshot Tool
cadgen snapshot renders the robot to a PNG still, using the same shared
CLI and headless browser runtime every rendering skill uses — so a snapshot matches what
the CAD Viewer shows.
cadgen snapshot path/to/robot.srdf review.png
Hand it the .srdf; it routes by suffix and renders the paired URDF's geometry — the same-folder .urdf whose <robot name> matches, exactly as cadgen srdf validate pairs them. No match, or more than one, is refused before anything renders, naming the robot name it looked for and the .urdf files it found. Pose the robot with --joint-values — {joint: degrees} JSON,
joints you do not name staying where the CAD Viewer opens the robot: each at its default, then
this SRDF's home group state if it declares one (the "jointValues" job field is the same
thing in a packet). The snapshot draws the robot with the viewer's own scene, so it shows what
the viewer shows, and a link mesh that cannot be loaded fails it rather than leaving the link
out. Robots are authored in metres and are framed on the robot scene scale automatically.
A normal snapshot uses the Solid preset and Light appearance; omitted groups inherit preset defaults.
Pass --display render for the shared photographic scene. Inline display JSON and
JSON files use grouped settings such as lighting, background, and floor;
appearance is light (default) or dark. Projection and focal length belong
in display.camera. Top-level --camera and --joint-values remain active in every display
mode. The display modes are solid and render: edges, clip, exploded, the
xray, hidden-line and wireframe modes and the hidden/off surface styles
describe a STEP model's CAD edges, parts and solids, and are refused by name here.
Link meshes are resolved relative to the description, so they must be present: an
unhydrated Git LFS pointer fails as "No link mesh loaded for robot". Run
git lfs checkout <mesh dir> first.
An SRDF's geometry comes from its paired URDF, so it has no snapshot door of its
own; the polymorphic cadgen snapshot routes one by suffix. The grammar is
cadgen snapshot TARGET [OUT] [flags], the same one every format door uses. Use
cadgen snapshot --help for the complete current interface.
References
- Authoring contract (structure, URDF pairing, golden skeleton):
references/authoring-contract.md - SRDF workflow (URDF table extraction, edit loop):
references/srdf-workflow.md - Planning ledger:
references/planning-ledger.md - Validation and verification recipe:
references/validation.md - End effectors:
references/end-effectors.md - Disabled collisions:
references/disabled-collisions.md