Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions .github/workflows/pin-consumers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
name: Pin SDK Consumers

on:
workflow_dispatch:
inputs:
version:
description: 'SDK version to pin, with or without leading v'
required: true
type: string
workflow_run:
workflows: ['Publish NPM']
types: [completed]

jobs:
pin-consumers:
name: pin consumers
runs-on: ubuntu-latest
if: >-
github.repository == 'kernel/kernel-node-sdk' &&
(github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'release'))
permissions:
contents: read

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Resolve SDK version
id: version
env:
DISPATCH_VERSION: ${{ github.event.inputs.version }}
WORKFLOW_RUN_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail

raw="${DISPATCH_VERSION:-${WORKFLOW_RUN_HEAD_BRANCH:-}}"
version="${raw#v}"

if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
if [ -n "$DISPATCH_VERSION" ]; then
echo "Invalid SDK version: $DISPATCH_VERSION" >&2
exit 2
fi

raw="$(gh release list --repo "$GITHUB_REPOSITORY" --limit 10 --json tagName,publishedAt --jq 'sort_by(.publishedAt) | last | .tagName')"
version="${raw#v}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release fallback picks wrong version

Medium Severity

When workflow_run triggers pinning, the resolved SDK version often comes from gh release list (newest publishedAt among the last 10 releases) because workflow_run.head_branch is usually not a semver tag for release-triggered Publish NPM runs. That version is not tied to the completed publish run, so a delayed or overlapping release can pin dashboard/MCP to a different @onkernel/sdk than the one just published.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 05d9182. Configure here.

fi

if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid SDK version: $version" >&2
exit 2
fi

echo "version=$version" >> "$GITHUB_OUTPUT"
echo "branch_suffix=${version//./-}" >> "$GITHUB_OUTPUT"

- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.ADMIN_APP_ID }}
private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }}
owner: kernel
repositories: kernel,kernel-mcp-server

- name: Configure git identity
run: |
git config --global user.name "kernel-internal[bot]"
git config --global user.email "260533166+kernel-internal[bot]@users.noreply.github.com"

- name: Pin dashboard dependency
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
SDK_VERSION: ${{ steps.version.outputs.version }}
BRANCH_SUFFIX: ${{ steps.version.outputs.branch_suffix }}
run: |
set -euo pipefail

repo="kernel/kernel"
workdir="$RUNNER_TEMP/kernel"
branch="automation/pin-node-sdk-$BRANCH_SUFFIX"

gh repo clone "$repo" "$workdir" -- --depth 1
cd "$workdir"
git switch -c "$branch"

node "$GITHUB_WORKSPACE/scripts/utils/pin-sdk-consumer.mjs" packages/dashboard/package.json "$SDK_VERSION"
bun install

if git diff --quiet; then
echo "Dashboard already pins @onkernel/sdk@$SDK_VERSION"
exit 0
fi

git add packages/dashboard/package.json bun.lock
git commit -m "chore(dashboard): pin @onkernel/sdk to $SDK_VERSION"
git push "https://x-access-token:${GH_TOKEN}@github.com/${repo}.git" "HEAD:$branch" --force-with-lease
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lease push blocks branch updates

Medium Severity

Consumer repos are cloned with --depth 1 and a new local branch is pushed with --force-with-lease, but the remote automation branch is never fetched. If automation/pin-node-sdk-* already exists on the remote, Git typically cannot establish a lease and rejects the push, so reruns that need to refresh the same branch (e.g. lockfile updates) fail after changes are committed locally.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 05d9182. Configure here.


if gh pr view "$branch" --repo "$repo" >/dev/null 2>&1; then
echo "Dashboard pin PR already exists for $branch"
else
gh pr create \
--repo "$repo" \
--base main \
--head "$branch" \
--title "chore(dashboard): pin @onkernel/sdk to $SDK_VERSION" \
--body "Pins the dashboard to @onkernel/sdk@$SDK_VERSION and refreshes bun.lock so dashboard consumes the newly released Node SDK exactly."
fi

- name: Pin MCP dependency
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
SDK_VERSION: ${{ steps.version.outputs.version }}
BRANCH_SUFFIX: ${{ steps.version.outputs.branch_suffix }}
run: |
set -euo pipefail

repo="kernel/kernel-mcp-server"
workdir="$RUNNER_TEMP/kernel-mcp-server"
branch="automation/pin-node-sdk-$BRANCH_SUFFIX"

gh repo clone "$repo" "$workdir" -- --depth 1
cd "$workdir"
git switch -c "$branch"

node "$GITHUB_WORKSPACE/scripts/utils/pin-sdk-consumer.mjs" package.json "$SDK_VERSION"
bun install

if git diff --quiet; then
echo "MCP already pins @onkernel/sdk@$SDK_VERSION"
exit 0
fi

git add package.json bun.lock
git commit -m "chore: pin @onkernel/sdk to $SDK_VERSION"
git push "https://x-access-token:${GH_TOKEN}@github.com/${repo}.git" "HEAD:$branch" --force-with-lease

if gh pr view "$branch" --repo "$repo" >/dev/null 2>&1; then
echo "MCP pin PR already exists for $branch"
else
gh pr create \
--repo "$repo" \
--base main \
--head "$branch" \
--title "chore: pin @onkernel/sdk to $SDK_VERSION" \
--body "Pins the MCP server to @onkernel/sdk@$SDK_VERSION and refreshes bun.lock so MCP consumes the newly released Node SDK exactly."
fi
45 changes: 45 additions & 0 deletions scripts/utils/pin-sdk-consumer.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env node

import fs from 'node:fs';
import path from 'node:path';

const [packageJSONPath, rawVersion] = process.argv.slice(2);

if (!packageJSONPath || !rawVersion) {
console.error('usage: pin-sdk-consumer.mjs <package.json path> <sdk version>');
process.exit(2);
}

const version = rawVersion.replace(/^v/, '');

if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
console.error(`invalid SDK version: ${rawVersion}`);
process.exit(2);
}

const resolvedPath = path.resolve(packageJSONPath);
let packageJSON;

try {
packageJSON = JSON.parse(fs.readFileSync(resolvedPath, 'utf8'));
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`failed to read ${resolvedPath}: ${message}`);
process.exit(1);
}

if (!packageJSON.dependencies || !Object.hasOwn(packageJSON.dependencies, '@onkernel/sdk')) {
console.error(`${resolvedPath} does not declare dependencies["@onkernel/sdk"]`);
process.exit(1);
}

const previous = packageJSON.dependencies['@onkernel/sdk'];
packageJSON.dependencies['@onkernel/sdk'] = version;

fs.writeFileSync(resolvedPath, `${JSON.stringify(packageJSON, null, 2)}\n`);

if (previous === version) {
console.log(`@onkernel/sdk already pinned to ${version} in ${resolvedPath}`);
} else {
console.log(`Pinned @onkernel/sdk in ${resolvedPath}: ${previous} -> ${version}`);
}
Loading