Skip to content

Commit 01dec9e

Browse files
lroolleclaude
andcommitted
feat: --goal launch receipts for ccx session attribution
- validate slug, export DEVA_GOAL (create env; attach restamps on fresh --goal only) - append JSONL receipt to $XDG_DATA_HOME/ccx/launches/ after the DRY_RUN gate; warning-only on failure, never blocks a launch - receipts are evidence: ccx data dir, not the ops ledger; ccx joins receipt->session by cwd+time (ccx side ships separately) Refs #499 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent b51d568 commit 01dec9e

3 files changed

Lines changed: 60 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- launch receipts (#499): `--goal SLUG` stamps intent at launch.
12+
Exports `DEVA_GOAL` into the container (create-time env; attach
13+
restamps when a fresh `--goal` is given) and appends one JSONL
14+
receipt host-side to `$XDG_DATA_HOME/ccx/launches/YYYY-MM-DD.jsonl`
15+
(`ts`, `goal`, `agent`, `cwd`, `container`, `source`). ccx joins
16+
receipt->session by cwd+time so sessions stop being born orphans.
17+
Receipt writes never block a launch; no `--goal` = zero change.
18+
1019
## [0.18.1] - 2026-07-28
1120

1221
### Fixed

DEV-LOGS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@
2323
- What: hoist generate_auth_tag() out of the container-name rewrite branch (it already produced `auth-default | auth-file-<stem> | api-key-<last4> | env` for naming and the `deva.auth_tag` label) and export it as `DEVA_AUTH_TAG` on every run. No naming/label change. Verified all three modes via scratch-HOME `--dry-run`.
2424
- Result: in-container tooling gets a stable account handle. Statusline side (chip + per-account cache dirs keyed on the tag) lands in claude-code-statusline. /status stays wrong by design until a per-account `.claude.json` overlay exists — follow-up candidate for `--auth-with`.
2525

26+
# [2026-07-27] Dev Log: --goal launch receipts #499
27+
- Why: every session is born an orphan — no record of which goal a launch serves, so attribution is post-hoc archaeology over JSONL. Stamp intent at launch instead; the ops-ledger loop (brief/debrief) needs this as its ground truth.
28+
- What:
29+
- `--goal SLUG` flag: slug validated `[a-z0-9][a-z0-9-]*` at parse; `DEVA_GOAL` exported at create; attach exec restamps only when a fresh `--goal` is passed (set-u-safe `${arr[@]+...}` expansion for the optional env pair).
30+
- `write_launch_receipt`: one JSONL line to `$XDG_DATA_HOME/ccx/launches/YYYY-MM-DD.jsonl`, after the DRY_RUN gate, warning-only on failure. Receipts are evidence, so they live in the ccx data dir — the ops ledger cites session IDs, it does not store evidence.
31+
- Result: verified live from a deva container against the host daemon: bad slug rejected, dry-run writes nothing, `-Q --goal smoke-goal claude -- --version` exits 0 and appends a correct receipt; `DEVA_GOAL=env-check` visible in assembled docker args. Not live-tested: the attach-exec restamp path (code-reviewed only). ccx-side join lands in thevibeworks/ccx.
32+
2633
# [2026-07-27] Dev Log: cloak release policy + closing the unverified VNC/daemon gap #456
2734
- Why: two loose ends before shipping. (1) The 2026-07-21 entry left "x11vnc serving RFB and a live daemon inside the built image" unverified — that env couldn't apt-install. (2) release.yml made the GitHub Release `needs` the cloak build, so a ~200MB Chromium bake per arch (arm64 under QEMU) sat on the critical path of every release, including patch releases that touch nothing in that layer.
2835
- What:

deva.sh

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ DEBUG_MODE=false
8989
DRY_RUN=false
9090
CODEX_BROWSER_MCP=false
9191
HOST_TMUX=false
92+
GOAL=""
9293

9394
if [ -t 0 ] && [ -t 1 ]; then
9495
DOCKER_TERMINAL_ARGS=(-it)
@@ -157,6 +158,10 @@ Deva flags:
157158
-Q, --quick Bare mode: no host config mounts, no .deva loading, no autolink,
158159
implies --rm. Like emacs -Q. Mutually exclusive with -c.
159160
--host-net Use host networking for the agent container
161+
--goal SLUG Stamp this launch with a goal slug ([a-z0-9-]):
162+
exports DEVA_GOAL in-container and appends a
163+
launch receipt to $XDG_DATA_HOME/ccx/launches/
164+
for ccx session attribution
160165
--host-tmux Opt in to container->host tmux: mounts ~/.ssh (ro)
161166
and passes DEVA_HOST_USER so the in-container
162167
deva-tmux CLI can reach the host tmux server.
@@ -3196,6 +3201,19 @@ parse_wrapper_args() {
31963201
i=$((i + 1))
31973202
continue
31983203
;;
3204+
--goal)
3205+
if [ $((i + 1)) -ge ${#incoming[@]} ]; then
3206+
echo "error: --goal requires a slug" >&2
3207+
exit 1
3208+
fi
3209+
GOAL="${incoming[$((i + 1))]}"
3210+
if ! [[ "$GOAL" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
3211+
echo "error: --goal slug must match [a-z0-9][a-z0-9-]* (got '$GOAL')" >&2
3212+
exit 1
3213+
fi
3214+
i=$((i + 2))
3215+
continue
3216+
;;
31993217
--browser-mcp | --codex-browser-mcp | --with-browser)
32003218
CODEX_BROWSER_MCP=true
32013219
i=$((i + 1))
@@ -4064,6 +4082,7 @@ done
40644082
DOCKER_ARGS+=(-e "DEVA_CONTAINER_NAME=${CONTAINER_NAME}")
40654083
DOCKER_ARGS+=(-e "DEVA_WORKSPACE=$(pwd)")
40664084
DOCKER_ARGS+=(-e "DEVA_EPHEMERAL=${EPHEMERAL_MODE}")
4085+
[ -n "$GOAL" ] && DOCKER_ARGS+=(-e "DEVA_GOAL=${GOAL}")
40674086
40684087
# Back up .claude.json before mounting, without touching live credential files.
40694088
if [ "$QUICK_MODE" != true ] && [ "$DRY_RUN" != true ]; then
@@ -4206,6 +4225,24 @@ if [ "$DRY_RUN" = true ]; then
42064225
exit 0
42074226
fi
42084227
4228+
# Launch receipt: one JSONL line per --goal launch, host-side, into the
4229+
# ccx data dir. Receipts are evidence (ccx joins receipt->session by
4230+
# cwd+time); the ops ledger cites session IDs, it does not store these.
4231+
# Never blocks a launch: a failed write is a warning, not an error.
4232+
write_launch_receipt() {
4233+
[ -n "$GOAL" ] || return 0
4234+
local dir="${XDG_DATA_HOME:-$HOME/.local/share}/ccx/launches"
4235+
if ! mkdir -p "$dir" 2>/dev/null; then
4236+
echo "warning: cannot create $dir; skipping launch receipt" >&2
4237+
return 0
4238+
fi
4239+
printf '{"ts":"%s","goal":"%s","agent":"%s","cwd":"%s","container":"%s","source":"deva"}\n' \
4240+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$GOAL" "${ACTIVE_AGENT:-}" "$(pwd)" "$CONTAINER_NAME" \
4241+
>>"$dir/$(date -u +%Y-%m-%d).jsonl" 2>/dev/null ||
4242+
echo "warning: failed to write launch receipt" >&2
4243+
}
4244+
write_launch_receipt
4245+
42094246
inject_workspace_context
42104247
42114248
if [ "$EPHEMERAL_MODE" = false ]; then
@@ -4274,17 +4311,22 @@ if [ "$EPHEMERAL_MODE" = false ]; then
42744311
_trace_env="DEVA_TRACE=0"
42754312
[ "${DEVA_TRACE_ACTIVE:-false}" = true ] && _trace_env="DEVA_TRACE=1"
42764313
4314+
# Same override rule for the goal: attach with a fresh --goal restamps
4315+
# the container; without it the create-time DEVA_GOAL (if any) stands.
4316+
_goal_env=()
4317+
[ -n "$GOAL" ] && _goal_env=(-e "DEVA_GOAL=${GOAL}")
4318+
42774319
# Trace UI reachability is fixed at container create (port publish);
42784320
# attaching to a container created without it cannot gain the mapping.
42794321
announce_trace_ui existing
42804322
announce_cloak_browser existing
42814323
announce_cloak_vnc existing
42824324
42834325
if [ "$AUTH_PROVISION_MODE" = true ]; then
4284-
docker exec -e "$_trace_env" "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}" || true
4326+
docker exec -e "$_trace_env" ${_goal_env[@]+"${_goal_env[@]}"} "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}" || true
42854327
finish_auth_provision
42864328
else
4287-
exec docker exec -e "$_trace_env" "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}"
4329+
exec docker exec -e "$_trace_env" ${_goal_env[@]+"${_goal_env[@]}"} "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}"
42884330
fi
42894331
else
42904332
echo "Launching ${ACTIVE_AGENT} (ephemeral mode) via $(docker_image_ref)"

0 commit comments

Comments
 (0)