The Publish Relay

One file, two lanes, no shared key

The Publish Relay

A sandboxed builder can finish the page without holding the account that can publish it. The estate passes an immutable revision across a guarded boundary, then a small publisher uses the MAIN login to put that exact revision on claude.ai.

01

Why the lanes are split

Credential split means the account secret and the ability to publish live only in the MAIN-login lane. A builder receives enough filesystem access to make the page, but not the claude.ai identity that owns the artifact.

Builder lane

Produces a finished HTML file

A Codex engine such as Terra or Sol has no Claude Artifact tool. An alternate-login Claude session is denied that tool by design. Both can write and revise local files, so creative work can happen without exposing MAIN credentials.

Publisher lane

Performs one narrow account action

A headless Haiku process runs under MAIN credentials with only Artifact and Read. Its gate fixes the order, file, URL, and allowed actions. It publishes; it does not edit the source.

One-way build/publish separation

The builder sends a content-addressed snapshot into the registry. The publisher reads that snapshot and reports the result. No reusable credential is handed to the builder, and the publisher cannot improvise a different page.

End-to-end artifact publishing swimlane A builder writes HTML and enqueues a revision. Systemd starts a constrained Haiku publisher under main credentials, which reads, publishes, reads back, and records completion. BUILDER Alt Claude or Codex No MAIN publishing credential REGISTRY Local handoff ledger Snapshot, claim, generation TRIGGERS systemd user units Wake the drain, never publish MAIN CREDENTIAL Haiku publisher → claude.ai Read, publish, verify, record write HTML finished local file begin + enqueue immutable rev SHA + base + queue start drain nudge / path / timer guarded publish live read → publish read-back → complete seq + remote SHA
Notice: only the last lane touches claude.ai, while the exact revision identity travels through every lane.
02

The registry is the handoff

The registry is a local, locked manifest plus snapshot folders. It answers three questions: what file is this, what exact revision is waiting, and what was last published?

begin marks the base

Before editing an existing page, begin writes the registry ID and the current published SHA into the source. That SHA is the builder's base, meaning “this is the version I started from.” A new page gets base none.

enqueue freezes a revision

The registry copies the page and supporting files into a directory named by their content SHA. That is an immutable revision: a snapshot whose identity changes if its content changes. A newer enqueue supersedes the older queued SHA without erasing its record.

claim fences a worker

A claim returns the ID, revision SHA, a fresh token, and the current publish generation. Completion is accepted only if all four still match. A late or duplicated worker cannot quietly complete somebody else's job.

queued_rev.sha
Exact snapshot waiting
base
Published SHA editing began from
claim token
One worker's lease number
publish_generation
Accepted publish counter
Registry entry state machine Queued revisions can be claimed, published, conflicted, or failed after three attempts. Retry and human merge return terminal attention states to queued. queued eligible work claimed token + gen lease published accepted result conflict human merge failed 3 attempts claim() complete + matching fence stale / mismatch / conflict() fail #3 fail #1 or #2 → queued retry() resets attempts no automatic retry main merges, then begin + enqueue changed revision enqueued expired claim → new token A newer enqueue during a claim remains queued after the claimed revision finishes.
Notice: conflict and failed ask for attention; only failed has an explicit retry command.
03

Three ways to wake one drain

A drain is the short process that claims eligible queue entries and runs the publisher for each. The wake-ups overlap on purpose, so one missed signal does not strand a revision.

Three publisher triggers Enqueue directly nudges the service, a path unit watches the queue folder, and an hourly timer retries eligible work. All converge on the same drain. WAKE-UP SOURCES 1. direct nudge enqueue asks systemctl fast path, failure ignored 2. folder watch queue becomes nonempty artifact-publisher.path 3. hourly sweep retry eligible leftovers not terminal failed entries trigger service 20 s quiet, then drain one process lock pending? registry check no exit no model call yes claim
Notice: the timer is a safety net, not a poller that keeps Haiku alive; an empty drain exits before any model call.

Why idle costs no model money

The timer starts an ordinary local process once an hour. If pending() is empty and the queue folder is empty, the drain returns immediately. A live smoke check measured a 27 ms idle drain with zero model calls.

What the sweep can recover

It can pick up queued work, an expired claim, or a missed folder notification. After three failed attempts the entry is failed and excluded from pending work until someone runs retry.

04

Inside the guarded publisher

Mode A is the selected headless mode: one non-interactive Haiku run under the default MAIN account. The live implementation keeps HOME and leaves CLAUDE_CONFIG_DIR unset, because explicitly pointing it at ~/.claude produced a warning in the measured CLI.

claude -p --model haiku --tools "Artifact,Read" \
  --permission-mode bypassPermissions --strict-mcp-config \
  --setting-sources "" --settings <run/settings.json> \
  --no-session-persistence --max-budget-usd 0.50 \
  --output-format json < /dev/null
MAIN credentials Artifact + Read only one publish reserved five minute process timeout $0.50 maximum
1 · base

The queued base must equal the registry's published SHA.

2 · live read

An update must read the planned URL into the planned directory.

3 · publish

The gate checks the exact snapshot path, files, URL, and live hash.

4 · read-back

The returned version is read, hashed, and only then marked complete.

The prompt is not the safety boundary

During testing, the model reordered the instructions and tried a stale read-back. A PreToolUse gate, a hook that runs before each tool call, now enforces the sequence. It also confines local reads to the run folder, refuses the wrong URL or files, permits one publish, and fails closed if its signed plan cannot be verified.

05

Two guards against overwriting

Compare-and-swap means “update only if the version I read is still live.” The claude.ai artifact service performs that check on the server. For interactive MAIN publishing, the estate also blocks the one explicit bypass.

Overwrite refusal flow A stale publish is refused by the artifact service and returns the live version for a merge. A force true attempt is independently denied before the tool runs. NORMAL UPDATE session reads v3 its comparison base another session publishes v4 v3-based publish arrives stale REFUSED v4 stays live live v4 returned merge, then publish again BYPASS ATTEMPT publish force: true would discard the newer version PreToolUse force deny hook DENIED BEFORE TOOL USE owner must remove the hook to force Measured 2026-09-26 with CLI 2.1.280: the stale publish changed nothing live.
Notice: the server protects the normal path; the estate hook closes the explicit force: true escape hatch for interactive MAIN sessions.

Built behavior beats the earlier plan

The phase 3 plan described a race between the publisher's live check and publish as merely detected afterward. A same-day measurement showed stronger behavior: claude.ai refused a stale publish on the server, left the newer version live, and returned it for merging. The registry generation check and main journal remain useful records and recovery fences.

Interactive MAIN publish

After a successful Artifact publish, a PostToolUse journal appends the tool event first. It then snapshots the source and records the URL, SHA, sequence number, and publisher identity in the registry. Recording errors go to an error log and do not undo the publish.

Why the journal matters

An interactive publish increments the registry's publish generation. If a headless worker still holds an older claim, its generation no longer matches, so completion becomes a conflict instead of blessing stale bookkeeping.

06

Glossary

The short version of the estate vocabulary used above.

Artifact
A web page hosted on claude.ai and owned by the account that published it.
Builder
A lane that creates the local page but cannot publish it.
MAIN login
The Claude account whose credentials own and update the estate's artifacts.
Registry
The local manifest, queue, and content-addressed revision store used for handoffs.
SHA
A cryptographic content fingerprint. Different bytes produce a different revision identity.
Claim
A time-limited lease containing a revision, token, and publish generation.
Generation
A counter advanced when the registry accepts a publish record.
Hook
A small program run immediately before or after a tool call to deny or record it.
systemd
The Unix service manager that starts the local drain from a nudge, path watch, or timer.
Compare-and-swap
A server check that accepts an update only when its read version is still current.