dwmkerr / dwmkerr/openspec-flow

feat: attach agent session.json to workflow run (with optional encryption)

Open
#73 3 comments 2 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
0
Forks
0
Avg merge
3d 23h
Merged PRs (30d)
1

Description

## Problem

When an OpenSpec Flow agent step silent-no-ops (see dwmkerr/livedown#48 — runs 24690447577 for dwmkerr/livedown#46 and 24692035267 for dwmkerr/livedown#48), the workflow surfaces nothing useful. `show_full_output: false` (the default on claude-code-action) hides the agent's reasoning and tool use. Debugging the root cause requires the session transcript, which currently lives only inside the runner and is discarded when the run completes.

This is specifically about the **OpenSpec Flow workflows** (`.github/workflows/openspec-flow.yaml` — the plan / implement / respond jobs) being able to save their own agent-step session logs as workflow run artifacts. The agent in each job is Claude Code running via `claude-code-action`; its session log (`claude-execution-output.json`) should optionally be attached to the workflow run. This is not about livedown's end-user application and not about any other workflow.

## Proposal

Attach the agent's `claude-execution-output.json` (the Claude Code session transcript) to the OpenSpec Flow workflow run as an artifact, so a maintainer can download it after the fact and reason about what the agent actually did.

### Config shape

Workflow-level inputs / env vars (snake_case):

- `attach_session_logs` — boolean flag. `no` (default) or `yes`. When `yes`, the agent transcript is uploaded as a workflow run artifact after each agent job.
- `encrypt_session_logs_password` — symmetric encryption. Accepts a password / passphrase sourced from a repo secret. When set, logs are encrypted with that password (e.g. `age -p` or equivalent symmetric mode) before upload.
- `encrypt_session_logs_key` — **future work, not day-one**. Key-based encryption (e.g. an `age` recipient public key or a GPG key). Called out here so the config namespace is reserved, but not in scope for the first cut.

### Why encryption matters

Session transcripts include:

- Every tool call (e.g. `printenv`, `gh api`) and its output — may echo env-injected secrets
- The full prompt (references to `GITHUB_TOKEN` and `ANTHROPIC_API_KEY`, though not the values themselves)
- Any file content the agent read, including `.env` if it touched one

Workflow run artifacts are **publicly downloadable** on public repos. Plaintext upload on a public repo is a secret-leak vector.

**Security rule:** if `attach_session_logs=yes` is set on a public repo, the workflow SHALL fail fast unless encryption is configured (i.e. `encrypt_session_logs_password` is set, or — once implemented — `encrypt_session_logs_key`). Plain-text upload on public repos is a refuse-case.

### Secret scrubbing (from issue discussion)

Per the design comment on this issue: rather than hand-maintain a list of env vars to scrub, the workflow should **deterministically enumerate the env vars and GitHub secrets that were provided to the agent step** and redact those values from the transcript before upload. The spec should call out:

- Which env / secret names are in scope (everything in the agent step's `env:` block, plus anything referenced via `${{ secrets.* }}`)
- How the values are discovered at runtime (e.g. iterate the step's env; pull `secrets` names from the workflow definition — values are not retrievable from inside the runner, so substitute by the known names that were exported)
- That scrubbing is a defence-in-depth layer on top of encryption, not a substitute for it

## Suggested implementation sketch

After the agent step in each job (plan / implement / respond), before label flipping:

```yaml
- name: Capture session logs
if: env.attach_session_logs == 'yes' && always()
run: |
cp "$RUNNER_TEMP/claude-execution-output.json" /tmp/session.json
# Scrub all env vars that were exposed to the agent step.
# (Names are enumerable from the step env; values are substituted by name.)
for name in $AGENT_EXPOSED_ENV_NAMES; do
val="${!name}"
[ -n "$val" ] && sed -i "s|$val|[REDACTED_${name}]|g" /tmp/session.json
done
if [ -n "$encrypt_session_logs_password" ]; then
age -p -o /tmp/session.json.age < /tmp/session.json
rm /tmp/session.json
fi

- name: Upload session log artifact
if: env.attach_session_logs == 'yes' && always()
uses: actions/upload-artifact@v4
with:
name: agent-session-${{ github.run_id }}-${{ github.job }}
path: /tmp/session.json*
retention-days: 14
```

## Acceptance

- A run with `attach_session_logs: yes` uploads the transcript as a run artifact for each agent job (plan / implement / respond)
- With `encrypt_session_logs_password` set, the artifact is encrypted with that password — decryptable only by whoever holds it
- Default (`attach_session_logs: no`) adds zero cost / zero artifact
- On a public repo, `attach_session_logs: yes` without any encryption config SHALL fail the workflow fast (hard refuse, not warn)
- Env vars exposed to the agent step are scrubbed from the transcript before upload, as a defence-in-depth layer
- `encrypt_session_logs_key` (key-based encryption) is explicitly listed as follow-on work, not day-one

## Related

- dwmkerr/livedown#48 preflight / postflight — session logs pair with postflight for deeper diagnosis
- dwmkerr/livedown#38 no-token security audit — whatever token-isolation pattern lands, the session log capture must keep matching the new reality

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading .github/workflows/openspec-flow.yaml and trace the plan, implement, and respond jobs around their claude-code-action steps. Check how claude-execution-output.json is produced and how actions/upload-artifact@v4 can run after each agent step. Done means optional capture works for all three jobs, encryption and scrubbing meet the stated rules, the default creates no artifact, and public plaintext upload is refused.

Written by the indexing model from the issue text.

Assessment

Tech stack
github-actions, shell
Domain
ci-cd, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.