feat(cli): teleport dira into cloud agent runtimes - #132
Merged
Conversation
azlekov
marked this pull request as ready for review
August 27, 2026 06:45
Contributor
|
Knowledge layer
Spec currency is reported in the job summary and never fails a build, the same severity the local pre-commit hook uses. |
azlekov
force-pushed
the
claude/dira-cli-cloud-runtime-3eb50e
branch
from
August 28, 2026 20:46
6684610 to
2f5f6af
Compare
azlekov
added a commit
that referenced
this pull request
Sep 2, 2026
Split `command_is_portable_wrapper` out of `command_invokes_hook` and make `event_is_wired` crate-visible: two upcoming callers need the wrapper / direct-form distinction rather than the union — `dira init` in merge mode must treat a committed portable entry as already wired instead of adding an absolute-path twin beside it, and the portable hook must yield to user-scope wiring only when that wiring is not itself a wrapper. Add `claude_user_settings_path()` honouring CLAUDE_CONFIG_DIR, used by both `dira init --global` and the wiring reader, so writer and reader follow Claude Code when its user config directory has been relocated instead of reporting a stale `~/.claude/settings.json` as live wiring. Why: prerequisite seam for the #132 review follow-ups (double hook delivery). Refs: DIRASH-0022 Spec: doctor Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Asen Lekov <asenlekoff@gmail.com>
azlekov
force-pushed
the
claude/dira-cli-cloud-runtime-3eb50e
branch
from
September 2, 2026 15:21
2f5f6af to
36c299f
Compare
azlekov
added a commit
that referenced
this pull request
Sep 2, 2026
Split `command_is_portable_wrapper` out of `command_invokes_hook` and make `event_is_wired` crate-visible: two upcoming callers need the wrapper / direct-form distinction rather than the union — `dira init` in merge mode must treat a committed portable entry as already wired instead of adding an absolute-path twin beside it, and the portable hook must yield to user-scope wiring only when that wiring is not itself a wrapper. Add `claude_user_settings_path()` honouring CLAUDE_CONFIG_DIR, used by both `dira init --global` and the wiring reader, so writer and reader follow Claude Code when its user config directory has been relocated instead of reporting a stale `~/.claude/settings.json` as live wiring. Why: prerequisite seam for the #132 review follow-ups (double hook delivery). Refs: DIRASH-0022 Spec: doctor Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Asen Lekov <asenlekoff@gmail.com>
azlekov
force-pushed
the
claude/dira-cli-cloud-runtime-3eb50e
branch
from
September 2, 2026 15:31
36c299f to
9eb6cdc
Compare
`SessionRollup` gains `runtime` (`claude-web`, `cursor-cloud`, …) and `runtimeSessionRef` (the harness's own session id), both optional and omitted when absent, so the dashboard can badge cloud-agent work and deep-link transcripts without inferring anything from device labels. Additive minor bump 1.3.0 → 1.4.0: an older cloud strips the unknown keys and gates on the schema major only, so rollout order is free. Still metadata-only — a runtime name and an opaque id, never content (D-0001). The batch builder leaves both fields unset; the daemon stamps them at flush time from the running environment. Batch ids never derive from session rollups, so the post-assembly stamp cannot perturb chunk identity or dedup. The signing vector populates both fields so the cross-language fixture exercises them; schema and vector regenerated via `just contract`. Refs: D-0001 Refs: D-0020 Spec: attestation-sync Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Asen Lekov <asenlekoff@gmail.com>
…and the flush-time runtime stamp Cloud agent runtimes route all egress through a TLS-intercepting proxy whose CA exists only inside the VM, and D-0011's bundled-roots-only trust store fails every HTTPS call there. `DIRA_EXTRA_CA_CERTS` (DIRASH-0033) names a PEM bundle whose certificates are *added* to the bundled roots by `dira_core::httpclient::builder()`, the one construction site every device→cloud client now uses. Never a store swap, never `SSL_CERT_FILE`; a missing or malformed bundle degrades to the default client with a warning, and a partially corrupt bundle keeps its valid certificates (one whole-bundle build first, per-block salvage only on failure). The sync debounce and backstop become clamped config knobs (`DIRA_SYNC_DEBOUNCE_SECS`, `DIRA_SYNC_BACKSTOP_SECS`, defaults 3/90 so the historical constants hold) so an ephemeral VM can flush eagerly and lose at most seconds on an abrupt reclaim. `DIRA_IDENTITY_EMAIL` overrides the attribution email for VMs whose git identity is the platform bot; it must carry an `@` and a plausible length. `dira_core::runtime::detect` reads the vendor marker or an explicit `DIRA_RUNTIME`, clamps both fields to 64 chars, and takes the Claude session ref only on the claude-web branch; the daemon stamps the result onto every rollup leaving the process, and outside a cloud runtime the keys stay off the wire, byte-identical to 1.3. Refs: DIRASH-0033 Refs: D-0011 Refs: D-0009 Refs: D-0019 Refs: D-0008 Refs: D-0018 Refs: D-0020 Refs: DIRASH-0031 Refs: D-0001 Spec: daemon-lifecycle Spec: attestation-sync Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Asen Lekov <asenlekoff@gmail.com>
`dira cloud init` generates portable, repo-committed capture wiring: `.dira/hook.sh` (run-time `dira` resolution, always exit 0), `.dira/bootstrap.sh` (in a cloud VM: digest-verified install of the pinned release, daemon start, runner-token device claim, then it forwards its own SessionStart event; on a laptop: straight to the forward), a `.gitattributes` pinning LF, and portable commands merged into the project `.claude/settings.json` / `.cursor/hooks.json`, replacing machine-specific `dira init` entries. It is repo-root anchored, idempotent, pins the release digests at generation time (`--no-pin` to opt out, a failed fetch warns and writes the unpinned form), refuses to overwrite an unparseable config when wiring several harnesses, and warns on dev builds, gitignored outputs, and Windows. Cursor cloud agents provision from `.cursor/environment.json` via `--install-only` / `--provision-only`, since cloud agents never run `sessionStart`. `dira device link --runner-token` (env `DIRA_RUNNER_TOKEN`) is the headless claim: same endpoint and invariants as the code claim, no TTY, bounded timeouts, the client nonce persisted across retries, the token never on argv. The committed portable hook yields to live user-scope wiring only when the project config also carries the portable wrapper (DIRASH-0037), so laptops with both never double-count and a stray env marker cannot drop events; `dira init` in merge mode treats a portable wrapper as already wired. `dira doctor` gains `cloud.runtime`, `cloud.reachability` (probed only inside a cloud runtime or on explicit `--check`, never a failure), `cloud.bootstrap`, and `hooks.scope_overlap`. `dira status --json` / `dira sessions --json` are the scriptable capture surface; errors are a JSON envelope on stdout. The bootstrap isolates its provisioning subprocesses from the hook's stdin, bounds every curl, prefers the embedded digest with the release `.sha256` as fallback (sha256sum → shasum → openssl), probes flock support, logs every skipped provisioning branch, and fails `--install-only` loudly while hook mode always degrades to "not instrumented". Refs: DIRASH-0022 Refs: DIRASH-0023 Refs: DIRASH-0029 Refs: DIRASH-0030 Refs: DIRASH-0033 Refs: DIRASH-0037 Refs: D-0003 Refs: D-0004 Refs: D-0006 Refs: D-0007 Spec: cloud-runtime Spec: doctor Spec: harness-sources Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Asen Lekov <asenlekoff@gmail.com>
…d runtime Every other test covers half the story; this job wires a throwaway repo with `dira cloud init --no-pin`, sets `CLAUDE_CODE_REMOTE=true` so the bootstrap takes its real cloud branch, runs Claude Code headless, and asserts on `dira status --json` that a session was captured and attributed to the fixture's canonical repo ref, and on the store that a session-start event landed. Needs a `CLAUDE_CODE_OAUTH_TOKEN` secret; without it (fork PRs) it exits 0 with a SKIP. Bounded by `--max-budget-usd`, path-scoped, GitHub-hosted, with the Claude Code CLI pinned instead of `latest`. Refs: D-0012 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Asen Lekov <asenlekoff@gmail.com>
Adds docs/cloud-runtimes.md and the `cloud-runtime` living spec (runner-token endpoint contract for dirahq-cloud, the measured TLS and egress behaviour of cloud runtimes, the yield rule, digest pinning, the doctor probe policy), records DIRASH-0033 (extra CA roots are an explicit, additive opt-in; the committed bootstrap may promote a runtime-declared CA file) and DIRASH-0037 (the portable hook yields to live user-scope wiring, two-sided, no cache, no time window), refreshes the doctor spec for the new checks and the D-0006 exemption, and regenerates the adapter files. Refs: DIRASH-0033 Refs: DIRASH-0037 Refs: DIRASH-0022 Refs: D-0006 Spec: cloud-runtime Spec: doctor Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Asen Lekov <asenlekoff@gmail.com>
azlekov
force-pushed
the
claude/dira-cli-cloud-runtime-3eb50e
branch
from
September 2, 2026 16:22
9eb6cdc to
0587797
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Agents increasingly run in cloud runtimes — Claude Code on the web and Cursor cloud agents execute sessions in ephemeral VMs — and none of that work is captured today:
dira init's hook configs embed a machine-specific absolute path, linking is interactive-only, and the VM's TLS-intercepting egress proxy defeats the binary's bundled-roots-only trust store (D-0011). This PR adds the repo side of capturing cloud agent work.Teleport
dira cloud initgenerates portable, repo-committed artifacts:.dira/hook.sh(run-timediraresolution, always exit 0) and.dira/bootstrap.sh(in a cloud VM: sha256-verified install of the pinned release from GitHub release assets, daemon start, runner-token device claim, then forwards its own SessionStart event; on a laptop: straight to the forward), plus portable hook commands merged into the project.claude/settings.json/.cursor/hooks.json— replacing machine-specificdira initentries rather than duplicating them. Idempotent/fixpoint, covered by a new e2e test.dira device link --runner-token(envDIRA_RUNNER_TOKEN): the headless claim variant — same endpoint and invariants as the code claim (server-assigned id, persist nothing until authoritative, nonce idempotency), no TTY anywhere on the path. The endpoint contract fordirahq-cloudis specified in.zavet/specs/cloud-runtime.md..cursor/environment.jsonrather than a hook, via two non-hook bootstrap modes:--install-only(build phase, cached) and--provision-only(boot phase: install, start the daemon, claim a device, forward nothing — there is no hook payload on stdin and it must not fabricate one). Cursor exposes no session-start hook, and its hooks are skipped during the agent's early read-only turns, so provisioning cannot hang off one.dira_core::runtime::detect(): conservative runtime detection (CLAUDE_CODE_REMOTE, explicitDIRA_RUNTIME) used for device labels, diagnostics, and the 1.4 rollup stamp below — capture and accounting are byte-identical everywhere.Contract 1.4: the runtime rides the wire
SessionRollup.runtime+runtimeSessionRef(additive minor,1.3.0 → 1.4.0): the cloud runtime the session ran in (claude-web,cursor-cloud, …) and the harness's own session reference, so the dashboard can badge cloud-agent work and deep-link transcripts. Both optional + omitted-when-absent; still metadata-only (a runtime name, never content).runtime::detect()— a property of the running environment, not of stored events, and batch ids never derive from session rollups, so the post-assembly stamp cannot perturb chunk identity or dedup. Outside a cloud runtime the keys stay off the wire, byte-identical to 1.3.just contractwith the drift gates green. An older cloud strips unknown keys and gates on schema major only, so rollout order is free.Transport, attribution, ephemerality
DIRA_EXTRA_CA_CERTS(new decision DIRASH-0033, amending D-0011): every device→cloud reqwest client builds throughdira_core::httpclient::builder(), which additively appends PEM anchors — never a store swap, neverSSL_CERT_FILE, and a bad bundle degrades to the default client with a warning. The bootstrap takes the CA the runtime itself declares ($SSL_CERT_FILE→$NODE_EXTRA_CA_CERTS→~/.ccr/ca-bundle.crt→ system bundle).DIRA_SYNC_BACKSTOP_SECS=15so an abruptly reclaimed VM loses at most ~15s of un-synced tail (batches were already idempotent with per-chunk cursors).DIRA_IDENTITY_EMAIL: attribution override for VMs whosegit config user.emailis the platform bot.Observability
dira doctorgainscloud.runtime,cloud.reachability(TLS failures name the extra-CA opt-in; other transport failures name the egress allowlist), andcloud.bootstrap.dira status --json/dira sessions --json:status --jsoncarries today's rollup, so it still reports sessions that have already ended — the scriptable "what did dira capture" surface.Measured, not assumed
Everything below was measured inside a live Claude Code cloud session:
invalid peer certificate: UnknownIssuer, and succeeds once the runtime's CA is added. Nothing to configure — the CA is environment-provided.app.dirahq.shis refused at the proxy withCONNECT tunnel failed, response 403, before TLS. That one does need the environment's Custom network access setting. The two failures are separate, and the docs now say which is which.claude -pheadless mode (SessionStart, UserPromptSubmit, Stop, SessionEnd) with no trust dialog, andCLAUDE_PROJECT_DIRis set. The committed hook command is nonetheless spelled${CLAUDE_PROJECT_DIR:-.}so an unset variable can't resolve hooks to/.dira/…and fail them.CI: a real agent session is captured
New
.github/scripts/cloud-capture-smoke.sh+cloud-capture.yml. Every other test covers half the story — the unit tests pin whatcloud initwrites,cloud_init_e2epins the artifacts,doctor --probedrives dira's own synthetic hook. None prove the part the design rests on: that a harness we do not control, wired only through committed config, fires hooks that land as counted events.The job wires a throwaway repo, sets
CLAUDE_CODE_REMOTE=trueso the bootstrap takes the real cloud branch, runs Claude Code headless, and asserts ondira status --jsonthat a session was captured and attributed to the fixture's canonical repo ref — which proves the writer's git enrichment ran, not merely that an event arrived.Needs a
CLAUDE_CODE_OAUTH_TOKENrepo secret (claude setup-token); without it — fork PRs, where GitHub exposes no secrets — it exits 0 with a SKIP so contributors never see a red check they cannot fix. Bounded by--max-budget-usd, path-scoped, GitHub-hosted runner.Test plan
just cipasses — run as its constituent commands (cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace— all 32 suites green)sh -nclean on both generated scripts;--provision-onlyverified to start the daemon, pick the runtime-declared CA, and leave the store empty (no phantom event), and to be a clean no-op outside a cloud runtimePASS — sessions=1 agent_seconds=6 project=github.com/dira-smoke/cloud-capture/contractchanged (schema 1.4, additive):just contractregeneratedattestation.schema.json+ the signing vector with the drift gates green; new serde test pins camelCase round-trip and byte-identical omission; the daemon stamp is covered by a dirad test that pins both branches (stamped inside a runtime, absent outside)runtime/runtimeSessionRefand was re-verified cross-language by the cloud's TS verifier (VALID — Rust signature verified by TypeScript over JCS(payload))install.sh/install.ps1: not applicable (shellcheck unavailable in this environment; the new scripts aresh -n/bash --posix -nclean)Follow-ups
dira init cursorwiressessionStart/sessionEnd, which are absent from Cursor's currently documented hook list — pre-existing behaviour incli/sources, worth verifying separately.Checklist
git commit -s) per the DCOcli, daemon, contract, ci, release, repo, deps(see CLAUDE.md)develop(the integration trunk), notmain🤖 Generated with Claude Code
https://claude.ai/code/session_01S6tcfjfNEYvR926UrwVvfn