**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 |
|---|---|---|
| cad | Create/edit parametric CAD models, organize CAD projects, export STEP/STL/3MF/GLB files, resolve prompt references, and measure geometry with cadgen. Open and visually review existing STEP/STP, STL, 3MF and GLB files in CAD Viewer. | MIT |
CAD modeling and inspection
Provenance: maintained in earthtojake/text-to-cad. Use the installed local skill files for the current interface.
Start with the task
Read only the references needed for the request.
| Task | First action | Reference |
|---|---|---|
| Create or edit a part or assembly | Find the existing Python model, or create a decorated model below; edit source and run python <model>.py. |
Model contract, shape construction; positioning for assemblies |
| Organize a CAD project | Follow its existing layout; for a new multi-model project use src/, format output folders, and a model catalog. |
Project layout, minimal starters |
| Export STL, 3MF or GLB | Add a mesh decorator for a maintained output, or run the format's build INPUT.step OUT command for a one-off export. |
Mesh exports |
| Resolve a reference from a prompt | Identify its saved STEP/STP document, open it with read_scene, and call scene.resolve(ref) as shown below. |
Reference syntax and inspection |
| Measure or check geometry | Write a Python check using native build123d geometry and, where useful, cadgen.geometry. |
Inspection and validation |
| Model from an image or drawing | Extract the specified dimensions and record meaningful assumptions. | Interpreting the request |
| Open an existing STEP/STP, STL, 3MF or GLB | Show it to the user. | Show the model |
| Review appearance or motion | Snapshot the saved document; use declared kinematics or animation for poses and clips. | Snapshots, kinematics |
| Diagnose a failure | Read the error and check the relevant model, geometry or command contract. | Repair loop, version migration |
| A message says to migrate | Do the migration now; an unmigrated model silently loses kinematics, materials and animation. | Version migration |
For 2D DXF drawings use $dxf; this skill owns any 3D part the drawing projects.
Use the corresponding robot-description skill for URDF, SRDF or SDF.
Setup and paths
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.
cadgen doctor <skill-dir> reports the installation in use and checks that it is
the one this skill pins, and that the CAD kernel loads; use it for installation or
kernel load errors. Use the relevant
subcommand's --help for additional flags.
Run project commands from the CAD project root. CLI input/output paths and
read_scene/read_step paths are working-directory-relative; decorator out=
paths are relative to the model script. Anchor file inputs on __file__
when the model must run from any directory.
Create or edit a model
A model is a plain Python script with a parameterless decorated function
returning a build123d shape. Use one model per entrypoint, with the script and
its declared outputs sharing a filename stem. For example, src/bracket.py:
from cadgen import build123d as bd
from cadgen import step
WIDTH = 40.0
@step(out="../STEP/bracket.step")
def bracket():
body = bd.Box(WIDTH, 20, 6)
body.label = "bracket"
return body
if __name__ == "__main__":
bracket()
python src/bracket.py
- Edit the model source when it exists, then run it to regenerate its outputs. Document export and snapshot commands take saved files and never run source.
- Keep meaningful dimensions explicit. Use millimeters and XY/+Z unless the task or project specifies another convention; choose a useful functional datum. Prefer closed, positive-volume solids for physical parts, while honoring requests for surfaces or construction geometry.
- Put parameterized geometry in ordinary factory functions; a decorated model
selects a configuration. Keep module bodies cheap: create geometry and read
CAD inputs inside the model or its helpers. Use the lazy
bdimport above; use postponed annotations when annotations mentionbdtypes. - Call child models inside the assembly model. Place their results with
.moved()orLocation * shapeto preserve shared geometry. Use meaningful occurrence labels and source-defined placements. Rerun the parent assembly to incorporate a changed child. - Read vendor STEP inputs with
cadgen.read_step. Every file a build opens is an input on its own, whatever reads it (json.load,np.load,bd.import_step, a project font): nothing is declared. Never read a model's own output as its input. Geometry must not depend on untracked time, random values, environment variables or the working directory. - When named purchasable parts are needed, search
$step-partsbefore making placeholders. Record an unsuccessful search and any placeholder assumptions.
For unfamiliar dimensions or interfaces, record the assumptions needed to model and verify them. Ask for missing information when it materially affects the requested result. Inspection and export requests do not need a modeling brief.
Mesh exports
Stack @stl, @threemf or @glb on the model for outputs that should be
maintained on every run. A model may declare only meshes; STEP is optional.
For a one-off export from an existing generated or imported STEP:
cadgen stl build STEP/bracket.step STL/bracket.stl
cadgen 3mf build STEP/bracket.step 3MF/bracket.3mf
cadgen glb build STEP/bracket.step GLB/bracket.glb
Omitting OUT writes one sibling file with the requested extension. It does not discover declared model variants. See mesh exports for decorator examples, mesh tolerances and animated GLB.
Prompt references and inspection
A reference such as /work/robot/STEP/assembly.step#o1.2.f7 identifies geometry
in a particular saved document. Its file part is the document's absolute path as
the CAD viewer copied it, with the file's real name and extension (in quotes when it
holds a space or #). Open that path with read_scene, and pass the whole reference
to resolve():
from cadgen import read_scene
scene = read_scene("/work/robot/STEP/assembly.step")
selection = scene.resolve("/work/robot/STEP/assembly.step#o1.2.f7")
face = selection.shape() # owned native geometry, in document world coordinates
print(selection.ref, face.area)
A note from the viewer's Quick Edit reads: what the person wants, then
File: (the document it is about), References: (one per line, as above) and,
when they sketched on the view, Sketch: <path>: a PNG of the view with their
markup (or the image itself, attached). Look at the sketch before changing the model.
For a bare #o1.2.f7, use the identified target file. Do not guess between
ambiguous files or labels. Numeric refs belong to that saved revision;
reopen and reselect after rebuilding. The inspection reference
covers label aliases, enumeration, measurements and small reusable operations.
There is no inspect CLI. Put exploratory checks in the project's ignored
tmp/ (or system /tmp/); retain reusable checks in checks/ or its existing
test directory. Keep them outside model-source and raw-output folders.
Verify and hand off
Choose checks from the requested dimensions, clearances and topology. For STEP
outputs, check the saved artifact with read_scene or read_step. For mesh-only
models, check the model's returned native geometry and review the mesh output;
do not add a STEP solely to satisfy the workflow. Report units, thresholds,
selected geometry and untested requirements. A failed computation is not a pass.
After creating or visibly changing geometry, generate and review at least one snapshot of the resulting STEP or mesh. Snapshots are your own review: always render and read them yourself, never rely on the viewer for it. Choose additional views to expose the features under review; see snapshot policy and options.
cadgen step snapshot STEP/bracket.step tmp/review.png
cadgen stl snapshot STL/bracket.stl tmp/mesh.png
Repair failures in the source and rerun the affected checks. Use geometry and
images for CAD comparisons; path-targeted git status is bookkeeping, not
geometric evidence. cadgen store why <model>.py explains unexpected rebuilds;
python <model>.py --force forces one model, and cadgen daemon status shows
build progress. More diagnostics are in the model contract.
Include output files, checks actually run, and material assumptions or limitations in the final response. The user sees the model in the viewer (Show the model), so don't attach snapshots unless they ask for an image. Explain any snapshot skip or failure using the cases in the snapshot reference.
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.
Generate changed artifacts first: the viewer never runs model scripts. Existing STEP files compile on open when needed. Topology selection and measurement require STEP; meshes support visual review.