Skip to content

Commit 7e07060

Browse files
Allow writes in CI for analysis.json to be commited
1 parent f20517f commit 7e07060

5 files changed

Lines changed: 63 additions & 16 deletions

File tree

.github/workflows/codeboarding.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ on:
1111
types: [created]
1212

1313
permissions:
14-
contents: read
14+
# write: the action commits the generated .codeboarding/analysis.json back to the
15+
# PR branch so the webview can open this PR's diff at the head SHA (same-repo PRs).
16+
contents: write
1517
pull-requests: write
1618
issues: write
1719

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ on:
6969
types: [created]
7070

7171
permissions:
72-
contents: read
72+
# write lets the action commit analysis.json to the PR branch so the comment can
73+
# link to the webview diff. Drop to `read` to keep the comment without that link.
74+
contents: write
7375
pull-requests: write
7476
issues: write
7577

@@ -156,6 +158,7 @@ The command needs the `issue_comment` trigger and runs from your default branch
156158
| `llm_api_key` | required | Your LLM provider API key (see `llm_provider`). |
157159
| `llm_provider` | `openrouter` | Provider for the key, mapped to `<NAME>_API_KEY` (e.g. `anthropic`, `openai`, `google`). |
158160
| `github_token` | `${{ github.token }}` | Token used to post or update the PR comment. |
161+
| `push_token` | `${{ github.token }}` | Token used to push the generated `analysis.json` to the PR branch (for the webview link). The workflow token can push when the workflow grants `permissions: contents: write`. Separate from `github_token` so commenting can use a GitHub App token while the push uses the workflow token. |
159162
| `engine_ref` | `v0.12.0` | CodeBoarding engine ref. Pin for reproducibility. |
160163
| `depth_level` | `1` | Analysis depth, 1 to 3. Higher is slower and richer. |
161164
| `render_depth` | `1` | Display depth for the PR diagram. Keep `1` for a clean top-level view. |

action.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ inputs:
1818
description: 'GITHUB_TOKEN used to post the PR comment. Defaults to the workflow token.'
1919
required: false
2020
default: ${{ github.token }}
21+
push_token:
22+
description: 'Token used to push the generated .codeboarding/analysis.json to the PR branch (for the webview link). Defaults to the workflow github.token, which can push when the calling workflow grants "permissions: contents: write". Kept separate from github_token so commenting can use a GitHub App token while the push uses the workflow token (whose write access the consumer controls).'
23+
required: false
24+
default: ${{ github.token }}
2125
engine_ref:
2226
description: 'Git ref (tag/branch/SHA) of CodeBoarding/CodeBoarding used as the analysis engine. Pinned to a release for reproducibility; override to track a newer ref.'
2327
required: false
@@ -605,7 +609,10 @@ runs:
605609
shell: bash
606610
working-directory: target-repo
607611
env:
608-
GH_TOKEN: ${{ inputs.github_token }}
612+
# Push with push_token (defaults to the workflow github.token, gated by the
613+
# consumer's `permissions: contents: write`) — NOT github_token, which may be
614+
# a GitHub App token used only for commenting and need not have write access.
615+
GH_TOKEN: ${{ inputs.push_token }}
609616
HEAD_DIR: ${{ steps.base.outputs.head_dir }}
610617
HEAD_REF: ${{ steps.guard.outputs.head_ref }}
611618
HEAD_SHA: ${{ steps.guard.outputs.head_sha }}
@@ -636,14 +643,16 @@ runs:
636643
637644
# Push to the PR head branch. The checkout used persist-credentials:false, so
638645
# authenticate the push explicitly with the workflow token (same-repo only).
646+
# Requires `contents: write` on the job's token — a read-only token (the
647+
# default) is rejected here; the push error below names the cause.
639648
AUTH_URL="https://x-access-token:${GH_TOKEN}@${SERVER_URL#https://}/${REPOSITORY}.git"
640-
if git push "$AUTH_URL" "HEAD:refs/heads/${HEAD_REF}" 2>/dev/null; then
649+
if git push "$AUTH_URL" "HEAD:refs/heads/${HEAD_REF}"; then
641650
NEW_SHA="$(git rev-parse HEAD)"
642651
echo "webview_sha=$NEW_SHA" >> "$GITHUB_OUTPUT"
643652
echo "ready=true" >> "$GITHUB_OUTPUT"
644653
echo "Committed head analysis to ${HEAD_REF} as ${NEW_SHA}."
645654
else
646-
echo "::warning::Could not push head analysis to ${HEAD_REF}; the webview link will be omitted."
655+
echo "::warning::Could not push head analysis to ${HEAD_REF}; the webview link will be omitted. Most likely the job's token lacks 'contents: write' (add 'permissions: contents: write' to the calling workflow), or the branch is protected against this pusher."
647656
fi
648657
649658
- name: Diff analyses → Mermaid

scripts/build_cta.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,20 @@ def main() -> int:
159159
issues = int(args.issues or 0)
160160
except ValueError:
161161
issues = 0
162-
print(build_cta(
163-
args.cta_base, args.owner, args.repo, args.pr, args.repo_path, issues,
164-
webview_base=args.webview_base, head_sha=args.head_sha, base_sha=args.base_sha,
165-
webview_ready=args.webview_ready,
166-
))
162+
print(
163+
build_cta(
164+
args.cta_base,
165+
args.owner,
166+
args.repo,
167+
args.pr,
168+
args.repo_path,
169+
issues,
170+
webview_base=args.webview_base,
171+
head_sha=args.head_sha,
172+
base_sha=args.base_sha,
173+
webview_ready=args.webview_ready,
174+
)
175+
)
167176
return 0
168177

169178

tests/test_build_cta.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,16 @@ def test_link_none_without_head_sha_or_base(self):
9898

9999
def test_cta_emits_webview_line_when_ready(self):
100100
out = bc.build_cta(
101-
"", "Org", "Repo", "9", repo_with(), issues=0,
102-
webview_base=self.WV, head_sha="headsha", base_sha="basesha", webview_ready=True,
101+
"",
102+
"Org",
103+
"Repo",
104+
"9",
105+
repo_with(),
106+
issues=0,
107+
webview_base=self.WV,
108+
head_sha="headsha",
109+
base_sha="basesha",
110+
webview_ready=True,
103111
)
104112
self.assertIn("Explore this PR", out)
105113
self.assertIn("ref=headsha", out)
@@ -108,17 +116,33 @@ def test_cta_emits_webview_line_when_ready(self):
108116
def test_cta_omits_webview_line_when_not_ready(self):
109117
# Fork PR / head analysis not committed -> webview can't fetch at head SHA.
110118
out = bc.build_cta(
111-
"", "Org", "Repo", "9", repo_with(), issues=0,
112-
webview_base=self.WV, head_sha="headsha", base_sha="basesha", webview_ready=False,
119+
"",
120+
"Org",
121+
"Repo",
122+
"9",
123+
repo_with(),
124+
issues=0,
125+
webview_base=self.WV,
126+
head_sha="headsha",
127+
base_sha="basesha",
128+
webview_ready=False,
113129
)
114130
self.assertNotIn("Explore this PR", out)
115131
# Editor CTA is still present regardless.
116132
self.assertIn("Open in VS Code", out)
117133

118134
def test_cta_omits_webview_line_when_ready_but_no_base_url(self):
119135
out = bc.build_cta(
120-
"", "Org", "Repo", "9", repo_with(), issues=0,
121-
webview_base="", head_sha="headsha", base_sha="basesha", webview_ready=True,
136+
"",
137+
"Org",
138+
"Repo",
139+
"9",
140+
repo_with(),
141+
issues=0,
142+
webview_base="",
143+
head_sha="headsha",
144+
base_sha="basesha",
145+
webview_ready=True,
122146
)
123147
self.assertNotIn("Explore this PR", out)
124148

0 commit comments

Comments
 (0)