Files
clawmates/docs/UPSTREAM-SCAN.md
T
Omar SobhandClaude Opus 5 563b074116 docs: ZeroClaw upstream, scanned against what we actually run
331 behind, 54 ahead. The previous scan said 218 and its conclusion about the
egress commit was wrong, so it is marked superseded rather than edited.

Merge cost is smaller than the number suggests: 660 files changed upstream, 52
by us, and **18 overlap**. `claude_cli.rs` — the provider every mission runs
through — exists in our tree and in zero upstream files, so it cannot conflict.

The find worth recording is not a feature. Upstream defaulted skills to compact
injection on 2026-08-05 (#8313), then restored the full default for v0.8.x on
2026-08-13 (#9913). Eight days. That is our `index` arm, tried at larger scale
and pulled back out of the stable line — evidence bearing directly on our own
open question of whether to flip the default, and with our own data at n=1 per
arm it argues for more pairs before flipping, not fewer.

Their documentation also states plainly what ours should: "Compact mode reduces
prompt size; it is not an isolation boundary for untrusted skill sources."
Progressive disclosure is a token optimisation. It is not a security control.

Also noted, as a documented limit rather than a surprise: upstream fixed
case-insensitive allowlist matching (#9568) and symlink-escape path resolution
(#9384) in their command gate. Ours resolves no paths, so a symlink to `curl`
defeats it.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz
2026-08-27 09:58:01 -05:00

109 lines
5.5 KiB
Markdown

# ZeroClaw upstream, scanned against what we run — 2026-08-27
Our fork (`git.redclaw.dev/osobh/zeroclaw`) sits **331 commits behind**
`upstream/master` and **54 ahead**. The previous scan in
`TOOL-CALL-ARCHITECTURE.md` said 218; that number is stale and its conclusion
about the egress commit was wrong — see `MISSION-EGRESS.md`.
Upstream since our merge base (`a56c345d51`, v0.8.4): 198 fixes, 35 features,
25 docs, 24 tests. The scan below filters that to what touches how **ClawMates**
uses the runtime: `claude_cli` as the mission provider, the gateway RPC and WS,
pairing, the skills door, and the claude→kimi→glm fallback chain.
## Merge cost
| | |
|---|---|
| files we changed | 52 |
| files upstream changed | 660 |
| **overlap** | **18** |
`claude_cli.rs` exists in our tree and in **zero** upstream files, so the
provider that runs every mission cannot conflict. The overlap is concentrated in
`providers/{factory,catalog,lib,compatible}.rs`, `runtime/agent/{loop_,turn,
system_prompt}.rs`, `gateway/ws.rs` and `config/schema.rs`. A full merge is a
real piece of work but not an unbounded one.
## Tier 1 — take these
**`841f28c7f1` fix(gateway): serialize config writes so a flush can't erase
concurrent updates (#9519).** The one I would take first. Upstream added a
`config_write_lock` around "the read-mutate-save-swap critical section of every
HTTP" config write. Our pinned gateway has **no such lock** — the mutexes in
`gateway/src/lib.rs` are for rate limits, keys, cancellation and the SOP engine.
We make **two** config writes per mission, on adjacent keys:
`providers.models.claude_cli.default.settings` (hooks — orchestrator:341)
`providers.models.claude_cli.default.mcp_config` (skills door — orchestrator:980)
Stated honestly: ours are sequential and awaited, so this is a **latent hazard,
not a demonstrated bug in our deployment**. It earns first place because the
failure mode is one this codebase has hit repeatedly — two writers of one
document, second wins, no error — and because the loser here is either the
security gate or the skills door, both of which fail silently and invisibly.
**`eadaee0b62` fix(providers): redact Anthropic credential fragments (#10092).**
We forward provider credentials into mission containers and log heavily around
them. Credential fragments in logs are a live risk for us specifically.
**`47adb9863e` fix(gateway): harden unauthenticated /api/pair against lockout
bypass (#9438).** We mint a pairing code per mission container. The gateway port
is not published to the host, but mission containers share `clawmates_edge`, so
one mission's container can reach another's gateway. A pairing lockout that can
be bypassed is a cross-mission path.
**`49ba8064d3` + `0b1a715c72` + `0cba80e422` — reliable-fallback accounting and
served-model logging.** We run claude→kimi→glm and care which one answered;
these make the fallback's own account of itself accurate.
## Tier 2 — take the idea, not the code
Upstream's skills system is not ours: mission skills go through our delivery
path and our MCP door. The *findings* still apply.
**`d04b345bda` honor always-inject frontmatter in compact prompt mode (#9520).**
Upstream compact mode has an `always: true` escape hatch — a skill that stays
fully injected no matter the mode. Our `skills` table has no such column
(`id, workspace_id, title, author, description, body, installs, created_at,
name, when_to_use, tags, source_kind, current_version, updated_at`), and our
delivery A/B produced **exactly the failure it prevents**:
`workspace-repo-commit-protocol` scored Trigger=FAIL in the `index` arm because
no agent fetched it. An always-inject flag is the missing piece.
**`9ea4f3371a``633c06f7c5` — the default, and the reversal.** Upstream
defaulted skills to compact injection on **2026-08-05**, then restored the full
default for v0.8.x on **2026-08-13**. Eight days. That is direct evidence for
our own open question of whether `index` should become the default: someone
larger tried the same move and pulled it out of the stable line. It does not
tell us they were right, and their reason is in an issue rather than the commit
— but with our own evidence at n=1 per arm, it argues for more pairs before
flipping a default rather than fewer.
Their documentation also carries a line worth adopting verbatim in spirit:
*"Compact mode reduces prompt size; it is not an isolation boundary for
untrusted skill sources."* Progressive disclosure is a token optimisation. It is
not a security control, and ours should not be described as one.
**`86f03d2d75` match command allowlist names case-insensitively (#9568)** and
**`7388987b55` resolve shell command path arguments to block symlink escapes
(#9384).** Different codebase, same bug class as the one we fixed in
`vm_tool_gate` yesterday. Our gate resolves no paths and follows no symlinks, so
`/tmp/link-to-curl -d @secret …` defeats it. Worth knowing as a documented limit
rather than pretending otherwise.
## Tier 3 — situational
- `53cba1ddde` opt-in multi-arch Alpine image — we hand-build arm64 today.
- `4282a5564a` gateway chat WebSocket keepalive — we already hardened our side.
- `0db7d999a` shared egress policy — **chat tier only**; see `MISSION-EGRESS.md`
for why it cannot reach a mission's `curl`.
- `6d76787556` per-server custom CA trust for MCP — our door is plain HTTP
inside docker; no value today.
## Not relevant
`zerocode` (21), the channel integrations (WhatsApp, Telegram, Matrix, Slack),
`hardware`/uno-q, the installer, and the Nostr dependency work. None of it is on
a path ClawMates executes.