Skip to content

feat(lanes): upload each reusable model leg's transcript as a sealed 7-day artifact - #969

Merged
kyle-sexton merged 4 commits into
mainfrom
feat/lane-transcript-upload-964
Nov 3, 2026
Merged

kyle-sexton merged 4 commits into
mainfrom
feat/lane-transcript-upload-964

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Closes #964

Summary

The model legs inside pr-review-cli.yml and intake-triage.yml kept no transcript, so the daily puller in melodic-software/claude-code-plugins#7676 had nothing to copy for them. After this PR, each run-claude-cli leg's execution file is scanned, sealed to the caller's public key and uploaded as a 7-day artifact. No plaintext transcript reaches a log or an artifact. Nothing changes for a caller until it passes the key.

Fix

  • New action .github/actions/upload-lane-transcript. It runs ../scan-upload/scan.cjs on the plaintext, gzips and seals the file in memory, and uploads only transcript.json.gz.sealed as lane-transcript-<job-id>-<attempt>, retention-days: 7, overwrite: false. The record is claude-code-plugins' seal-lane-output format, bound to the run id and job id.
  • New optional input lane-transcript-public-key on both reusables, default empty. An empty key skips the scan and the seal with a notice. In the model action the scan runs first; here it does not, because callers float on @main with no key until the follow-up below lands, and every leg would otherwise clone claude-code-plugins and could raise a scan error for a transcript nobody keeps.
  • Steps. Each leg gets one step with if: always() && steps.<leg>.outputs.execution-file != '', continue-on-error: true and timeout-minutes: 3. That is 4 per job in the three pr-review-cli.yml model jobs and 1 per job in the three intake-triage.yml model jobs. A sealing failure or a malformed key fails only that step.
  • Placement. The steps run after the outcome step, not directly after the leg. report-lane-outcome/action.yml:360-362 compares the clock at the outcome step with step-started-at, so upload time before it could turn a failed leg near its budget into timeout.
  • Job limits. Each model job's limit grows by what its new steps can take: review 95/103 to 107/115 (code/security), intake 16 to 19. continue-on-error does not stop a job timeout from turning a lane red.
  • Job ids. <lane>-<github.job> plus -2, -3, -4 for the chunk legs (code-review-key-2), and intake-<github.job> (intake-subscription). github.job must stay an expression because the parity tests deepEqual the three jobs' steps. In a reusable it is the called job's key: real artifacts read pr-review-cli-handoff-code-review-subscription-<run>-<sha>. The lane prefix keeps ids apart if one run calls both lanes, as this file's other artifact names already do. The puller's ARTIFACT regex (scripts/lane-transcripts/store.mjs) is greedy up to the last -, so lane-transcript-code-review-key-2-1 parses as job code-review-key-2, attempt 1.
  • Docs. The run-claude-cli execution-file output no longer says "do not upload it" and now points at this action. Both SECURITY MODEL headers, both canonical callers and the README action list name the new step.

No README or package.json for the action, as with scan-upload and run-claude-cli; its action.yml description holds the contract. The seal code is CommonJS, since this repository has no .mjs and CI globs *.test.cjs, and it keeps only the single-record path (no bundle or chunk code).

Verification

  • Compatibility with the real puller code (one-off script, not committed): sealed with this action, then opened with claude-code-plugins origin/main open-lane-output/open.mjs openRecord and named with store.mjs parseArtifactName, for all 27 job ids: opened 27 of 27 job ids, every name parsed, every plaintext byte-identical, transcriptMetrics read each one.
  • node --test on upload-lane-transcript-seal.test.cjs, intake-triage-legs, pr-review-cli, lane-outcome-emitters, network-timeout-policy, github-script-bodies, ci-fanout-consolidation and run-claude-cli/lane.test.cjs: 261/261 pass. New tests assert every leg has an upload with a unique, parseable job id. The seal test lives in .github/scripts, so the existing test-contracts glob runs it with no workflow edit; that also lets the review lanes run here, since claude-code-action skips itself when pr-require-checks.yml differs from main.
  • node --test on the 11 other suites that read these workflows (claude-lane-job-gates, claude-review-declared-outputs, claude-review-outcome-wiring, pr-review-cli-api-calls, report-api-budget, check-key-credit, intake-triage-apply, intake-triage-gate, claude-lane-status-check, claude-lane-fallback, claude-lane-install-isolation): all pass.
  • actionlint from the repository root on the three changed workflows: no findings.
  • zizmor --offline on the two reusables and the new action.yml: no findings (7 suppressed).
  • composite-run-shellcheck.sh on the new action.yml: 2 run blocks checked, no findings.
  • biome check on the edited tests, markdownlint-cli2 README.md, typos: clean.
  • Left out: a live sealed upload, which needs a caller passing the key (see Remaining); the rest of .github/scripts/*.test.cjs, which CI runs.

Related

Remaining

  • Runner policy, first: add lane-transcript-public-key to allowedInputs of both reusables' entries in melodic-software/standards components/runner-policy/policy.json, synced to .github/standards/runner-policy/policy.json in each repository. Neither entry lists it today, so the caller edit below would fall outside the policy's allowlist. The policy pins no job limit, so the limit changes here need no policy edit.
  • Caller edit in melodic-software/claude-code-plugins, not in this PR: add lane-transcript-public-key: ${{ vars.LANE_TRANSCRIPT_PUBLIC_KEY }} to the with: of the pr-review job in pr-review-hosted.yml, the pr-review-security job in pr-review-security-hosted.yml and the intake-triage job in intake-triage.yml. Until it lands, every leg only prints the skip notice. The close of claude-code-plugins#7676 depends on it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VQb9Q1VgLEiwGgRVshK3b7

…7-day artifact

Add the upload-lane-transcript action: it scans a leg's execution file,
seals it in claude-code-plugins' seal-lane-output record format to the
caller's public key, and uploads only the sealed file as
lane-transcript-<job-id>-<attempt> for 7 days. An empty key skips the
scan and the seal with a notice.

pr-review-cli.yml and intake-triage.yml take a new optional
lane-transcript-public-key input and run the action for every
run-claude-cli leg, after the outcome step, whose timeout check reads
the clock. Each model job's limit grows by the new steps' limits.

Closes #964

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQb9Q1VgLEiwGgRVshK3b7
@kyle-sexton
kyle-sexton marked this pull request as ready for review November 2, 2026 23:53
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-02T23:55:05.862913Z 3445cf7 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

kyle-sexton and others added 2 commits November 2, 2026 19:14
The ordering check passed if the outcome step was renamed, since findIndex
returns -1. One test name promised behaviour it only pins as step conditions.
The intake comment no longer held once a sealed transcript leaves the job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQb9Q1VgLEiwGgRVshK3b7
test-contracts already runs .github/scripts/*.test.cjs, so the seal test needs
no new step. Leaving pr-require-checks.yml unchanged lets the review lanes run
on this pull request: claude-code-action skips itself when the calling workflow
differs from the default branch's copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQb9Q1VgLEiwGgRVshK3b7
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

claude-security-review reviewed 155848268805132ca3843768a26fe4cc9eb6b560: 0 finding(s), 0 as inline comments.

Incremental review of .github/scripts/upload-lane-transcript-seal.test.cjs (since 1f5acaa). The change is formatting only: it reflows the makeTemporaryDirectory import and wraps the path.join calls for the action.yml read and the seal-transcript.cjs spawn path. The paths and arguments are the same, and no logic, environment, or trust-control code changed. The file is a test, not a trust control.

No security findings.

critical-findings: 0

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

claude-review reviewed 155848268805132ca3843768a26fe4cc9eb6b560: 0 finding(s), 0 as inline comments.

No findings. I reviewed only the change since 1f5acaa, which is in .github/scripts/upload-lane-transcript-seal.test.cjs.

The change is formatting only, in three places:

  • The makeTemporaryDirectory import is collapsed to one line.
  • The fs.readFileSync call that loads action.yml is wrapped across lines.
  • The seal-transcript.cjs path argument to spawnSync is wrapped across lines.

Reading the diff, the arguments, the paths and the control flow are the same before and after. No test behaviour changes.

I did not run node --check or the tests: the sandbox blocked the command and I did not retry it. The conclusion rests on reading the diff.

I did not re-review the other files of the PR; they are unchanged since the earlier review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQb9Q1VgLEiwGgRVshK3b7
@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 328b0c1 Nov 3, 2026
29 checks passed
@kyle-sexton
kyle-sexton deleted the feat/lane-transcript-upload-964 branch November 3, 2026 00:28
kyle-sexton added a commit to melodic-software/standards that referenced this pull request Nov 3, 2026
…nput (#942)

Refs: melodic-software/claude-code-plugins#7676

## Summary

Adds `lane-transcript-public-key` to the reviewed `allowedInputs` of
`intake-triage.yml` and `pr-review-cli.yml` in
`components/runner-policy/policy.json`, so a caller that passes it
clears `runner-policy`.

## Fix


[melodic-software/ci-workflows#969](melodic-software/ci-workflows#969)
added the optional input to those two reusables (`default: ""`). Without
a contract entry, `runner-policy` refuses a caller that passes it: "the
reusable workflow call has inputs absent from its reviewed contract".
The value is a public key and does not affect runner routing. Callers
float `@main` (`runner-policy.mjs:490-493`), so `approvedShas` does not
change.

This PR does not change the hosted caller components.
`components/claude-lanes-hosted/claude-review.yml:84` calls
`pr-review.yml@main` and `claude-security-review.yml:77` calls
`pr-review-security.yml@main`, not `pr-review-cli.yml`, and neither
reusable declares this input on ci-workflows main. Adding the line there
fails two `runner-policy.test.mjs` tests:

- "each hosted claude lane caller equals its fleet sibling"
- "every managed target of a hosted claude lane caller is public and
audits clean", with the contract message above

In this organization `pr-review-cli.yml` is called by locally owned
workflows, in claude-code-plugins and pr-pipeline-sandbox
(`distribution/sync-manifest.yml:880-882` for the sandbox).

## Verification

- `node --test components/runner-policy/runner-policy.test.mjs`: 226/226
pass.
- `node components/runner-policy/runner-policy.mjs --root .`: "Runner
policy passed."
- `biome check --config-path=biome.jsonc
components/runner-policy/policy.json`: no errors, schema-version notices
only.
- Probe, not committed: the two caller lines above, without any
`policy.json` entry for `pr-review.yml`, fail the two named tests.

## Related

-
[melodic-software/ci-workflows#969](melodic-software/ci-workflows#969):
the input.
-
[#940](#940):
the same policy-only change for `generated-copies`.

## Remaining

- After the standards sync carrying this `policy.json` reaches
claude-code-plugins (`distribution/sync-manifest.yml:382`), pass the
input from that repository's callers:
`.github/workflows/intake-triage.yml:258`, `pr-review-hosted.yml:220`
and `pr-review-security-hosted.yml:225`. Before that sync, its
`runner-policy` refuses them.
- The hosted caller components in this repository get no transcript
input until their reusables declare it or they move to
`pr-review-cli.yml`. That is an open decision.

<!-- session: lane-5 -->

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01VQb9Q1VgLEiwGgRVshK3b7

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(lanes): upload each reusable model leg's transcript as a sealed 7-day artifact

1 participant