fix(publish): authenticate to GitHub Packages before installing - #92
Conversation
A package whose own dependencies live on GitHub Packages could not be released. Both publish blocks ran `bun install` with no registry credentials, so a private first-party dependency failed with 401 then 403 and the publish step never ran. connect-basic hit this on v0.10.0: the version was bumped and the GitHub Release created, but no artifact was ever published. The token is now written to ~/.npmrc before install. Deliberately credentials only — no scope mapping. The existing ordering comment explains why the owner's scope must not be remapped this early: a package being published to both registries for the first time would stop resolving its own npmjs.org scope-siblings. Supplying a token for the host without touching the registry mapping fixes the install and preserves that property, because a repository that genuinely routes a scope to GitHub Packages already declares it in its own .npmrc. This is latent for any published package with a first-party dependency, not specific to connect-basic — hono-ts uses this workflow and carries @photon-hq/error as a devDependency. publish-npm has the same shape and is not covered here: it only accepts an npm-token, so giving it GitHub Packages credentials means a new input, and no package currently publishes to npmjs.org with a private first-party dependency. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughBoth GitHub Packages publishing actions now write the provided authentication token to ChangesGitHub Packages authentication
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
📚 Skills documentation may need an updateThis PR introduces changes that might not be reflected in the skills documentation. Reason: The PR adds two new public BuildSpace composite blocks for publishing single-package and monorepo packages to GitHub Packages, but
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/blocks/publish-github-package/action.yaml:
- Around line 51-57: The GitHub Packages authentication steps in
.github/blocks/publish-github-package/action.yaml lines 51-57 and
.github/blocks/publish-github-packages/action.yaml lines 49-55 must stop writing
tokens to $HOME/.npmrc. Use a temporary NPM_CONFIG_USERCONFIG file for each bun
install, export it for the install, and ensure the temporary file is deleted on
normal completion and failure via cleanup handling.
- Around line 44-57: The GitHub Packages authentication steps in
.github/blocks/publish-github-package/action.yaml lines 44-57 and
.github/blocks/publish-github-packages/action.yaml lines 45-55 must configure
the GitHub Packages registry before bun install for every install root. Add or
require the appropriate per-scope registry mapping while retaining npmjs.org
resolution for dual-registry packages; preserve the existing token
authentication behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c2756ee6-64f4-4b7e-8aef-801fe641321e
📒 Files selected for processing (2)
.github/blocks/publish-github-package/action.yaml.github/blocks/publish-github-packages/action.yaml
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
- GitHub Check: Cursor Bugbot
- GitHub Check: check-skills / check-skills
- GitHub Check: check-readme / check-readme
| # Credentials only, no scope mapping. A package whose own dependencies | ||
| # live on GitHub Packages needs a token to install them, but remapping the | ||
| # owner's scope here would break a package being published to both | ||
| # registries for the first time — its npmjs.org scope-siblings would stop | ||
| # resolving. Supplying the token without touching the registry mapping | ||
| # fixes the former and preserves the latter; a repository that routes a | ||
| # scope to GitHub Packages does so in its own .npmrc. | ||
| - name: Authenticate to GitHub Packages | ||
| shell: bash | ||
| env: | ||
| GITHUB_PACKAGES_TOKEN: ${{ inputs.github-token }} | ||
| run: | | ||
| printf '//npm.pkg.github.com/:_authToken=%s\n' \ | ||
| "$GITHUB_PACKAGES_TOKEN" >> "$HOME/.npmrc" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 8 'publish-github-packages?' .github/workflows || true
mapfile -t npmrc_files < <(fd -H -t f '^\.npmrc$' .)
if ((${`#npmrc_files`[@]} == 0)); then
echo "No .npmrc files found; inspect lockfiles or explicit registry URLs." >&2
exit 1
fi
if ! rg -n -C 2 'npm\.pkg\.github\.com|:registry=' "${npmrc_files[@]}"; then
echo "No GitHub Packages registry mapping was found." >&2
exit 1
fiRepository: photon-hq/buildspace
Length of output: 8844
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- action definitions ---'
for f in .github/blocks/publish-github-package/action.yaml \
.github/blocks/publish-github-packages/action.yaml; do
if [ -f "$f" ]; then
echo "### $f"
cat -n "$f"
fi
done
printf '%s\n' '--- workflow call sites and relevant inputs ---'
rg -n -C 12 'uses: .*publish-github-packages?|working-directory:|root-build-command:|build-command:' .github/workflows
printf '%s\n' '--- package and registry configuration files ---'
fd -H -t f '(^\.npmrc$|^bun\.fig$|^bunfig\.toml$|^package\.json$|^bun\.lockb$|^bun\.lock$|^npm-shrinkwrap\.json$|^package-lock\.json$|^yarn\.lock$|^pnpm-lock\.yaml$)' . \
--exclude node_modules --exclude dist --exclude build | sort
printf '%s\n' '--- registry references ---'
rg -n -C 3 'npm\.pkg\.github\.com|:registry=|registry' . \
-g '!node_modules/**' -g '!dist/**' -g '!build/**' \
-g '.npmrc' -g 'bunfig.toml' -g 'bunfig.*' -g 'package.json' \
-g '*.lock' -g '*.lockb' -g '*.yaml' -g '*.yml' || trueRepository: photon-hq/buildspace
Length of output: 32993
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- tracked package and registry files ---'
git ls-files | grep -E '(^|/)(\.npmrc|bunfig\.toml|package\.json|bun\.lockb?|package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|yarn\.lock)$' || true
printf '%s\n' '--- all filesystem package and registry files ---'
find . -type f \( \
-name .npmrc -o -name bunfig.toml -o -name 'package.json' \
-o -name 'bun.lock' -o -name 'bun.lockb' -o -name package-lock.json \
-o -name npm-shrinkwrap.json -o -name pnpm-lock.yaml -o -name yarn.lock \
\) -not -path './.git/*' -print
printf '%s\n' '--- all GitHub Packages references, including hidden paths ---'
rg -uuu -n -C 3 'npm\.pkg\.github\.com|:registry=|registry' . \
-g '!./.git/**' | head -n 300 || true
printf '%s\n' '--- install commands and their working-directory context ---'
rg -uuu -n -C 5 'bun install|npm install|yarn install|pnpm install|working-directory' \
.github . -g '!./.git/**' | head -n 300 || trueRepository: photon-hq/buildspace
Length of output: 48104
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
import re
actions = [
Path(".github/blocks/publish-github-package/action.yaml"),
Path(".github/blocks/publish-github-packages/action.yaml"),
]
config_names = {
".npmrc", "bunfig.toml", "package.json", "bun.lock", "bun.lockb",
"package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock",
}
configs = [
p for p in Path(".").rglob("*")
if p.is_file() and ".git" not in p.parts and p.name in config_names
]
for action in actions:
text = action.read_text()
install = text.index("run: bun install")
setup = text.index("uses: actions/setup-node@v4")
auth = text.index("Authenticate to GitHub Packages")
assert auth < install < setup, action
if "publish-github-package/action.yaml" in str(action):
assert "working-directory: ${{ inputs.working-directory }}" in text[install - 300:install + 100]
root = "inputs.working-directory"
else:
assert "run: bun install" in text
root = "repository root"
print(f"{action}: auth < install < setup-node; install root = {root}")
print("Registry/config files in this repository:")
for path in configs:
print(path)
if not configs:
print("(none)")
PYRepository: photon-hq/buildspace
Length of output: 439
Ensure each install root selects GitHub Packages.
Both actions write only the token. bun install runs before actions/setup-node creates the registry mapping. If the single-package inputs.working-directory or monorepo root lacks an existing .npmrc or equivalent mapping, private dependencies resolve against npmjs.org and fail. Add or require the mapping before installation while preserving dual-registry support.
📍 Affects 2 files
.github/blocks/publish-github-package/action.yaml#L44-L57(this comment).github/blocks/publish-github-packages/action.yaml#L45-L55
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/blocks/publish-github-package/action.yaml around lines 44 - 57, The
GitHub Packages authentication steps in
.github/blocks/publish-github-package/action.yaml lines 44-57 and
.github/blocks/publish-github-packages/action.yaml lines 45-55 must configure
the GitHub Packages registry before bun install for every install root. Add or
require the appropriate per-scope registry mapping while retaining npmjs.org
resolution for dual-registry packages; preserve the existing token
authentication behavior.
| - name: Authenticate to GitHub Packages | ||
| shell: bash | ||
| env: | ||
| GITHUB_PACKAGES_TOKEN: ${{ inputs.github-token }} | ||
| run: | | ||
| printf '//npm.pkg.github.com/:_authToken=%s\n' \ | ||
| "$GITHUB_PACKAGES_TOKEN" >> "$HOME/.npmrc" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate files ---'
git ls-files '.github/blocks/*publish-github*/*'
printf '%s\n' '--- package action ---'
sed -n '1,120p' .github/blocks/publish-github-package/action.yaml
printf '%s\n' '--- packages action ---'
sed -n '1,120p' .github/blocks/publish-github-packages/action.yaml
printf '%s\n' '--- related npm configuration and install usage ---'
rg -n -C 4 'npmrc|NPM_CONFIG_USERCONFIG|bun install|npm install|yarn install|pnpm install|npm\.pkg\.github\.com' .github/blocks .github/workflows 2>/dev/null || trueRepository: photon-hq/buildspace
Length of output: 20654
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
files = [
Path(".github/blocks/publish-github-package/action.yaml"),
Path(".github/blocks/publish-github-packages/action.yaml"),
]
for path in files:
lines = path.read_text().splitlines()
auth = next(
i for i, line in enumerate(lines)
if 'printf \'//npm.pkg.github.com/:_authToken=%s\\n\'' in line
)
install = next(
i for i, line in enumerate(lines)
if re.search(r'\brun:\s*bun install\s*$', line)
)
cleanup = [
i + 1 for i, line in enumerate(lines)
if any(token in line for token in ('rm -f', 'unset NPM_CONFIG_USERCONFIG', 'NPM_CONFIG_USERCONFIG='))
]
home_write = any(
'$HOME/.npmrc' in line and ('>>' in line or '>' in line)
for line in lines
)
print(path)
print(f" token_write_line={auth + 1}")
print(f" install_line={install + 1}")
print(f" token_write_precedes_install={auth < install}")
print(f" writes_user_npmrc={home_write}")
print(f" cleanup_or_temp_config_lines={cleanup}")
print(f" later_steps_after_install={len(lines[install + 1:]) > 0}")
PY
printf '%s\n' '--- action invocations ---'
rg -n -C 3 'publish-github-package(s)?/action\.yaml|publish-github-package(s)?@' .github . 2>/dev/null \
-g '!node_modules' -g '!dist' -g '!build' | head -200 || trueRepository: photon-hq/buildspace
Length of output: 3611
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- package action post-install section ---'
sed -n '100,130p' .github/blocks/publish-github-package/action.yaml
printf '%s\n' '--- monorepo action post-install section ---'
sed -n '56,90p' .github/blocks/publish-github-packages/action.yaml
printf '%s\n' '--- workflow context after composite actions ---'
sed -n '215,275p' .github/workflows/typescript-monorepo-release.yaml
sed -n '235,285p' .github/workflows/typescript-service-release.yamlRepository: photon-hq/buildspace
Length of output: 5510
Do not persist GitHub Packages credentials in $HOME/.npmrc.
Both actions append the token before bun install and leave it in the user-wide npm configuration. Use a temporary NPM_CONFIG_USERCONFIG file for each install and delete it on exit, including failure paths.
📍 Affects 2 files
.github/blocks/publish-github-package/action.yaml#L51-L57(this comment).github/blocks/publish-github-packages/action.yaml#L49-L55
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/blocks/publish-github-package/action.yaml around lines 51 - 57, The
GitHub Packages authentication steps in
.github/blocks/publish-github-package/action.yaml lines 51-57 and
.github/blocks/publish-github-packages/action.yaml lines 49-55 must stop writing
tokens to $HOME/.npmrc. Use a temporary NPM_CONFIG_USERCONFIG file for each bun
install, export it for the install, and ensure the temporary file is deleted on
normal completion and failure via cleanup handling.
The problem
A published package whose own dependencies live on GitHub Packages cannot be released. Both publish blocks run
bun installwith no registry credentials:connect-basic hit this on v0.10.0. The version was bumped and the GitHub Release created, but no artifact was ever published — a tagged release pointing at nothing.
This is latent for any published package with a first-party dependency.
hono-tsuses this workflow and carries@photon-hq/erroras a devDependency.The fix
Write the token to
~/.npmrcbefore install — credentials only, no scope mapping.The existing comment explains why the owner's scope must not be remapped this early:
That property is preserved. Supplying a token for the host is orthogonal to which scope routes there: a repository that genuinely routes a scope to GitHub Packages already declares it in its own
.npmrc, and one that does not is unaffected because nothing resolves to that host in the first place.Applied to both
publish-github-package(single) andpublish-github-packages(monorepo).Not covered
publish-npmhas the same shape but only accepts annpm-token, so giving it GitHub Packages credentials means adding an input. No package currently publishes to npmjs.org with a private first-party dependency, so I left it — happy to add it if you'd rather close the gap now.Note
connect-basic is separately dropping the dependency that surfaced this (connect-ts#8) because a chassis package shouldn't couple its release to a sibling's registry. That unblocks it independently of this PR; this one is the general fix.
🤖 Generated with Claude Code
Note
Low Risk
CI-only composite action ordering change; no runtime app code, and it preserves the existing deferred scope-mapping behavior.
Overview
Adds a pre-install GitHub Packages auth step to the
publish-github-packageandpublish-github-packagescomposite actions sobun installcan fetch first-party dependencies fromnpm.pkg.github.comwithout failing with 401/403.The new step appends only
//npm.pkg.github.com/:_authToken=…to~/.npmrcusing the existinggithub-tokeninput. It deliberately does not remap the repository owner’s npm scope to GitHub Packages—that mapping still happens later viaactions/setup-node, preserving the behavior where early scope remapping would break dual-registry first-time publishes.Comments in both action files were tightened to explain why credentials precede install while scope routing stays deferred.
Reviewed by Cursor Bugbot for commit f321435. Bugbot is set up for automated code reviews on this repo. Configure here.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by CodeRabbit