Weaknesses, ranked

Warts, ordered by how much they should affect a decision to adopt or trust. Two recurring themes organize most of the individual findings; they come first.

Theme A: one security boundary carries the whole load

The single most important thing to understand before trusting dsh as a sandbox: there is exactly one real enforcement boundary — the kernel file/process sandbox — and several capabilities live outside it. Consolidated from capabilities-and-security.html:

None of this is hidden — every gap is documented in a nearby code comment. But a reader who takes "sandboxed agent" at face value will substantially over-trust the system. Treat dsh's confinement as "keeps file writes in the workspace on a trusted machine", not "safe to point at hostile input".

Theme B: the gates verify freshness-of-generation, not truth-of-generator

dsh has the most elaborate doc-and-quality machinery we have seen in a production repo — and the drift we found clusters precisely inside the generated and gated artifacts, because a freshness gate proves a document matches its generator's output, never that the generator is right:

The lesson is not that the machinery is bad — it is better than most. It is that automation created a false sense of coverage: the gates are load-bearing for format and freshness and silent about correctness, and the team's trust in green gates let generator bugs and hand-edits of "generated" files through. Their own postmortem 0002 states the same principle from the test side: "A snapshot refresh is fixture production, not correctness review."

Individual findings

1. Two god files

packages/host/apiproxy/src/api-proxy.ts is 3,744 lines holding the entire host API surface — larger than the three core loop packages combined. packages/core/tools/src/index.ts is 1,946 lines holding registry, scoping, presentation, schema validation, code-mode transport, approval, and the whole execution pipeline in one class with five parallel WeakMaps. Both are the kind of file that becomes a merge-conflict magnet and a comprehension tax at this velocity.

2. CI has live holes, acknowledged in TODOs

No hosted post-merge validation right now (serial-linux/serial-macos are if: false), zero hosted macOS signal anywhere, and the blocking Windows check runs under Wine on Ubuntu while the real-Windows job is non-blocking — so a real-Windows-only regression can merge (ci.yml:338-342, :620-622). For a product shipping a Windows CLI with ACL sandboxing, this is a genuine gap.

3. The 100% coverage gate is hollowed out for the client half

51 of 69 coverage exclusions are packages/client/*, plus extensions, self-modification, and the Typert generator, with four TODO(gui) debt blocks and one negated-glob exclusion (vitest.config.ts:169-268). The headline "100% or it doesn't merge" is true for host/core and materially untrue for the GUI surface — which is also the lead product surface.

4. Advertised surfaces that do not ship

--profile tui appears in --help and the README but has no bundle; it is a third-party GitHub repo (turtle-ui). --host/--trusted-host are dead flags from the CLI (the schema rejects the only value that would activate them), yet carry two whole test files and elaborate authority-canonicalization code. Real engineering pointed at capability that cannot be reached through a supported invocation.

5. The same framework bug worked around five times

Vendored Cordis emit starves later listeners on a sync throw (vendor/cordis/src/events.ts:194-196); harness code hand-rolls the same containment block in five places rather than fixing the framework it owns. The duplication detector misses it because identifiers differ.

6. Unbounded loops with no core ceiling

The step retry loop is while (true) with bounding delegated entirely to listeners (agent.ts:339); a misbehaving agent/request-error listener spins forever. The driver then swallows every failure at its boundary with no logger call (agent.ts:212-215), so a failed turn can produce zero operator-visible output.

7. .agents/ is 22% of repo bytes that never ship and go unexplained

~714k English words plus Chinese twins, paid by every clone, unmentioned in the README, with a 428-file frozen-archive link graveyard (36% dead links by policy) and internal telemetry/registry hostnames and Figma URLs annotated as internal. No credentials leak, but the reconnaissance surface is real.

8. Developer-preview instability with no compatibility floor

AGENTS.md:7 and SESSION_FORMAT_VERSION = 0 state that nothing is contractual and backends reject old on-disk formats. Eight releases in one day, a mid-flight version reset, and an access-visibility flip between rc's. Stored sessions have no migration path. This is honest and appropriate for a preview — but it means anything built on today's dsh will break.

9. Fragile patterns worth watching

JSON.stringify structural equality in the load-bearing log invariant (key-order-sensitive false failures); a non-null assertion guarding what gets written to the durable log (agent.ts:318-319); invalid tool-call JSON degraded silently to a raw string (tool-calls.ts:104-110); parameter schemas open by default, forcing every tool to re-defend by hand; and scope bookkeeping in process-global WeakMaps that split silently if the package double-loads.

10. Documentation edges the machine does not reach

BENCHMARK.md is 3 lines with no data and is governed by no gate; a release-blocking FIXME ships in reference docs (docs/subsystems/llm-streaming.md:597); two TODOs ride inside the generated config catalog; three subsystem pages are missing from the hand-maintained website manifest; and the postmortem corpus — the best self-knowledge in the repo — is not published on their own site. Only four postmortems exist for 12,294 commits, which reads as under-recording rather than few incidents.