Skip to content

Commit fe2dac5

Browse files
authored
Restore Claude release session reports (#1314)
* Restore Claude release session reports * Redact credentials from Claude session reports --------- Co-authored-by: Dan Barr <6922515+danbarr@users.noreply.github.com>
1 parent c4633bd commit fe2dac5

4 files changed

Lines changed: 375 additions & 2 deletions

File tree

‎.github/workflows/_static-checks.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
run: python3 -m unittest discover -s .github/scripts -p 'test_*.py'
3333

3434
- name: Test release docs helpers
35-
run: node --test scripts/upstream-release/render-pr-body.test.mjs scripts/upstream-release/workflow.test.mjs
35+
run: node --test scripts/upstream-release/render-pr-body.test.mjs scripts/upstream-release/workflow.test.mjs scripts/upstream-release/post-session-report.test.mjs
3636

3737
- name: Test screenshot summary helpers
3838
run: node --test scripts/update-pr-screenshot-summary.test.mjs scripts/pr-screenshot-workflow.test.mjs

‎.github/workflows/upstream-release-docs.yml‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,19 @@ jobs:
709709
" 2>/dev/null || true)
710710
echo "dirs=$DIRS" >> "$GITHUB_OUTPUT"
711711
712+
# Load the reporter from the same trusted revision as this workflow,
713+
# before either session can edit files in the PR checkout.
714+
- name: Prepare session reporter
715+
id: session_reporter
716+
env:
717+
WORKFLOW_SHA: ${{ github.workflow_sha }}
718+
run: |
719+
REPORTER=$(mktemp "$RUNNER_TEMP/release-session-report.XXXXXX.mjs")
720+
git show "$WORKFLOW_SHA:scripts/upstream-release/post-session-report.mjs" > "$REPORTER"
721+
echo "path=$REPORTER" >> "$GITHUB_OUTPUT"
722+
712723
# Dispatch runs do not support Claude's live tracking comments.
713-
# Post explicit started/completed comments for reviewers instead.
724+
# Publish each final response explicitly after its session finishes.
714725
- name: Post skill-started comment
715726
if: github.event_name == 'workflow_dispatch' && steps.eff.outputs.number != ''
716727
env:
@@ -856,6 +867,20 @@ jobs:
856867
if: always() && steps.generation_token.outcome == 'success'
857868
run: git remote set-url origin "https://github.com/$GITHUB_REPOSITORY.git"
858869

870+
# Publish immediately: editorial overwrites the shared execution file.
871+
# Use the job token so reporting does not depend on session credentials.
872+
- name: Post generation report
873+
if: always() && steps.eff.outputs.number != '' && (steps.skill_gen.outcome == 'success' || steps.skill_gen.outcome == 'failure')
874+
continue-on-error: true
875+
env:
876+
REPORTER: ${{ steps.session_reporter.outputs.path }}
877+
EXECUTION_FILE: ${{ steps.skill_gen.outputs.execution_file }}
878+
PR_NUMBER: ${{ steps.eff.outputs.number }}
879+
SESSION: generation
880+
SESSION_OUTCOME: ${{ steps.skill_gen.outcome }}
881+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
882+
run: node "$REPORTER"
883+
859884
# Capture skill_gen's execution stats BEFORE skill_review runs
860885
# and overwrites the shared execution-output JSON at the
861886
# canonical claude-code-action location. Lets us report
@@ -909,6 +934,17 @@ jobs:
909934
identity: upstream-release-docs
910935
domain: octo-sts.stacklok.dev
911936

937+
# A failure before editorial starts must not publish generation's
938+
# response a second time through the action's fallback file output.
939+
- name: Clear previous session output
940+
if: always() && steps.skill_gen.conclusion == 'success' && steps.review_token.outcome == 'success'
941+
env:
942+
EXECUTION_FILE: ${{ steps.skill_gen.outputs.execution_file }}
943+
run: |
944+
if [ -n "$EXECUTION_FILE" ]; then
945+
rm -f -- "$EXECUTION_FILE"
946+
fi
947+
912948
- name: Run docs-review on skill output (fresh context)
913949
id: skill_review
914950
if: always() && steps.skill_gen.conclusion == 'success' && steps.review_token.outcome == 'success'
@@ -1006,6 +1042,18 @@ jobs:
10061042
if: always() && steps.review_token.outcome == 'success'
10071043
run: git remote set-url origin "https://github.com/$GITHUB_REPOSITORY.git"
10081044

1045+
- name: Post editorial report
1046+
if: always() && steps.eff.outputs.number != '' && (steps.skill_review.outcome == 'success' || steps.skill_review.outcome == 'failure')
1047+
continue-on-error: true
1048+
env:
1049+
REPORTER: ${{ steps.session_reporter.outputs.path }}
1050+
EXECUTION_FILE: ${{ steps.skill_review.outputs.execution_file }}
1051+
PR_NUMBER: ${{ steps.eff.outputs.number }}
1052+
SESSION: editorial
1053+
SESSION_OUTCOME: ${{ steps.skill_review.outcome }}
1054+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
1055+
run: node "$REPORTER"
1056+
10091057
# claude-code-action works in a sibling scratch checkout (the
10101058
# .claude-pr/ dir we gitignore) and pushes commits to origin
10111059
# directly, without advancing the outer workflow checkout's
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env node
2+
// SPDX-FileCopyrightText: Copyright 2026 Stacklok, Inc.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
import { execFileSync } from 'node:child_process';
6+
import { readFileSync } from 'node:fs';
7+
import { pathToFileURL } from 'node:url';
8+
9+
// Match the credential formats redacted by the pinned claude-code-action's
10+
// src/github/utils/sanitizer.ts (374f254f5efaf968abfe15d399fd90972e8138b9).
11+
// Actions log masking does not apply to the GitHub API request body.
12+
function redactSecrets(content) {
13+
return content
14+
.replace(/gh[pousr]_[A-Za-z0-9]{36}\b/g, '[REDACTED_GITHUB_TOKEN]')
15+
.replace(/github_pat_[A-Za-z0-9_]{11,221}\b/g, '[REDACTED_GITHUB_TOKEN]')
16+
.replace(/sk-ant-[A-Za-z0-9_-]{20,}/g, '[REDACTED_ANTHROPIC_KEY]')
17+
.replace(
18+
/(?:\b|(?<=\\(?:[nrtbf"\\/]|u[0-9a-fA-F]{4}))|(?<=\[[0-9;]*m))(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,
19+
'[REDACTED_AWS_KEY_ID]'
20+
)
21+
.replace(/xox[abpsr]-[A-Za-z0-9-]{10,}/g, '[REDACTED_SLACK_TOKEN]')
22+
.replace(
23+
/eyJ[A-Za-z0-9_-]{10,2000}\.eyJ[A-Za-z0-9_-]{10,4000}\.[A-Za-z0-9_-]{10,2000}\b/g,
24+
'[REDACTED_JWT]'
25+
);
26+
}
27+
28+
export function renderSessionReport(messages, env) {
29+
// Only the SDK's final result is suitable for a PR comment. Assistant
30+
// messages can contain intermediate narration; never fall back to them.
31+
const results = Array.isArray(messages) ? messages : [messages];
32+
const result = results.findLast((message) => message?.type === 'result');
33+
const response =
34+
typeof result?.result === 'string'
35+
? redactSecrets(result.result.trim())
36+
: '';
37+
const label = { generation: 'generation', editorial: 'editorial review' }[
38+
env.SESSION
39+
];
40+
if (!label) throw new Error('Unknown session');
41+
const header = `## Claude ${label} report\n\n**Session status:** ${env.SESSION_OUTCOME} · [Workflow run](${env.RUN_URL})\n\n`;
42+
if (!response) {
43+
return `${header}> Final response unavailable. See the workflow run for session details.\n`;
44+
}
45+
46+
// Leave room below GitHub's comment limit, including for Unicode text.
47+
const limit = 60000 - header.length;
48+
const suffix =
49+
'\n\n> Report truncated. See the workflow run for the full response.';
50+
const body =
51+
response.length > limit
52+
? response.slice(0, limit - suffix.length) + suffix
53+
: response;
54+
return header + body + '\n';
55+
}
56+
57+
export function postSessionReport(env) {
58+
if (!/^[1-9][0-9]*$/.test(env.PR_NUMBER ?? ''))
59+
throw new Error('PR_NUMBER must be a positive integer');
60+
if (!/^[\w.-]+\/[\w.-]+$/.test(env.GITHUB_REPOSITORY ?? ''))
61+
throw new Error('GITHUB_REPOSITORY must be owner/repo');
62+
let messages;
63+
try {
64+
messages = JSON.parse(readFileSync(env.EXECUTION_FILE, 'utf8'));
65+
} catch {
66+
// Missing or malformed output must never cause a transcript dump.
67+
messages = [];
68+
}
69+
const body = renderSessionReport(messages, env);
70+
execFileSync(
71+
'gh',
72+
[
73+
'api',
74+
'--method',
75+
'POST',
76+
`repos/${env.GITHUB_REPOSITORY}/issues/${env.PR_NUMBER}/comments`,
77+
'--input',
78+
'-',
79+
],
80+
{ input: JSON.stringify({ body }), encoding: 'utf8' }
81+
);
82+
}
83+
84+
if (
85+
process.argv[1] &&
86+
import.meta.url === pathToFileURL(process.argv[1]).href
87+
) {
88+
postSessionReport(process.env);
89+
}

0 commit comments

Comments
 (0)