ci: split plugin ClawHub publishing paths

* feat: partition clawhub plugin release candidates

* fix: read clawhub trusted publisher config endpoint

* feat: split clawhub plugin bootstrap workflow

* ci: split plugin clawhub publish paths

* ci: pin clawhub package publish workflow

* ci: keep clawhub bootstrap token out of builds

* ci: fix clawhub release dry-run gating

* ci: align clawhub oidc publish refs

* ci: make clawhub bootstrap recovery idempotent

* ci: route clawhub repair candidates through bootstrap

* ci: preserve tideclaw alpha clawhub guards

* ci: simplify clawhub release ref handling

* ci: extract clawhub release routing plan

* ci: extract clawhub release runtime state

* test: guard clawhub release helper executability

* ci: pin ClawHub CLI for plugin publishing

* ci: allow historical ClawHub dry-run validation

* ci: fix ClawHub bootstrap token handoff
This commit is contained in:
Patrick Erichsen
2026-06-12 20:16:06 -07:00
committed by GitHub
parent ded3a93058
commit 6cf06e8e7e
12 changed files with 2197 additions and 211 deletions
@@ -0,0 +1,314 @@
// OpenClaw release ClawHub plan script supports release workflow routing.
import { resolve } from "node:path";
import {
collectPluginClawHubReleasePlan,
type PublishablePluginPackage,
} from "./plugin-clawhub-release.ts";
import {
parsePluginReleaseSelection,
parsePluginReleaseSelectionMode,
type PluginReleaseSelectionMode,
} from "./plugin-npm-release.ts";
type ClawHubPlanPackage = Pick<PublishablePluginPackage, "packageName">;
type ClawHubDispatchInputs = Record<string, string>;
type ClawHubDispatchTarget = {
workflow: "plugin-clawhub-release.yml" | "plugin-clawhub-new.yml";
ref: string;
shouldDispatch: boolean;
packages: string[];
inputs: ClawHubDispatchInputs;
};
export type OpenClawReleaseClawHubPlanArgs = {
releaseTag: string;
releasePublishBranch: string;
releasePublishRunId: string;
pluginPublishScope: PluginReleaseSelectionMode;
plugins: string[];
};
export type OpenClawReleaseClawHubPlan = {
clawHubWorkflowRef: string;
releasePublishBranch: string;
normal: ClawHubDispatchTarget;
bootstrap: ClawHubDispatchTarget;
summary: {
normalCount: number;
bootstrapCount: number;
missingTrustedPublisherCount: number;
normalPlugins: string;
bootstrapPlugins: string;
missingTrustedPlugins: string;
};
verifier: {
clawHubWorkflowRef: string;
};
};
export type OpenClawReleaseClawHubRuntimeStateArgs = {
repository: string;
waitForClawHub: boolean;
forceSkipClawHub: boolean;
normalRunId?: string;
bootstrapRunId?: string;
bootstrapCompleted: boolean;
};
export type OpenClawReleaseClawHubRuntimeState = {
verifierArgs: string[];
proofLines: {
normal: string;
bootstrap: string;
};
};
function requireArg(value: string | undefined, label: string): string {
const trimmed = value?.trim();
if (!trimmed) {
throw new Error(`${label} is required.`);
}
return trimmed;
}
function packageNames(packages: readonly ClawHubPlanPackage[]): string[] {
return packages.map((plugin) => plugin.packageName);
}
function joinPackageNames(packages: readonly string[]): string {
return packages.join(",");
}
function optionalArg(value: string | undefined): string | undefined {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
}
function runUrl(repository: string, runId: string): string {
return `https://github.com/${repository}/actions/runs/${runId}`;
}
function assertNoPackageOverlap(
normalPackages: readonly string[],
bootstrapPackages: readonly string[],
) {
const normalPackageSet = new Set(normalPackages);
const overlap = bootstrapPackages.filter((packageName) => normalPackageSet.has(packageName));
if (overlap.length > 0) {
throw new Error(
`ClawHub release plan routed package(s) to both normal and bootstrap workflows: ${overlap.join(", ")}.`,
);
}
}
function createDispatchTarget(params: {
workflow: ClawHubDispatchTarget["workflow"];
ref: string;
packages: readonly string[];
releasePublishRunId: string;
releasePublishBranch: string;
includePublishScope: boolean;
}): ClawHubDispatchTarget {
if (params.packages.length === 0) {
return {
workflow: params.workflow,
ref: params.ref,
shouldDispatch: false,
packages: [],
inputs: {},
};
}
const plugins = joinPackageNames(params.packages);
return {
workflow: params.workflow,
ref: params.ref,
shouldDispatch: true,
packages: [...params.packages],
inputs: {
...(params.includePublishScope ? { publish_scope: "selected" } : {}),
plugins,
release_publish_run_id: params.releasePublishRunId,
release_publish_branch: params.releasePublishBranch,
},
};
}
export function buildOpenClawReleaseClawHubRuntimeState(
args: OpenClawReleaseClawHubRuntimeStateArgs,
): OpenClawReleaseClawHubRuntimeState {
const repository = requireArg(args.repository, "repository");
const normalRunId = optionalArg(args.normalRunId);
const bootstrapRunId = optionalArg(args.bootstrapRunId);
const shouldIncludeNormalRun =
!args.forceSkipClawHub && normalRunId !== undefined && args.waitForClawHub;
const shouldIncludeBootstrapRun =
!args.forceSkipClawHub && bootstrapRunId !== undefined && args.bootstrapCompleted;
const shouldVerifyClawHubPackages =
bootstrapRunId !== undefined &&
args.bootstrapCompleted &&
(normalRunId === undefined || args.waitForClawHub);
const shouldSkipClawHubPackages =
args.forceSkipClawHub || !(shouldIncludeNormalRun || shouldVerifyClawHubPackages);
const verifierArgs = shouldSkipClawHubPackages ? ["--skip-clawhub"] : [];
if (shouldIncludeNormalRun) {
verifierArgs.push("--plugin-clawhub-run", normalRunId);
}
if (shouldIncludeBootstrapRun) {
verifierArgs.push("--plugin-clawhub-bootstrap-run", bootstrapRunId);
}
let normalProofLine = "- plugin ClawHub publish: no normal OIDC candidates";
if (normalRunId !== undefined && args.waitForClawHub) {
normalProofLine = `- plugin ClawHub publish: ${runUrl(repository, normalRunId)}`;
} else if (normalRunId !== undefined) {
normalProofLine = `- plugin ClawHub publish: dispatched separately, not awaited by this proof: ${runUrl(repository, normalRunId)}`;
}
let bootstrapProofLine = "- plugin ClawHub bootstrap: not needed";
if (bootstrapRunId !== undefined && (args.bootstrapCompleted || args.waitForClawHub)) {
bootstrapProofLine = `- plugin ClawHub bootstrap: ${runUrl(repository, bootstrapRunId)}`;
} else if (bootstrapRunId !== undefined) {
bootstrapProofLine = `- plugin ClawHub bootstrap: dispatched separately, not awaited by this proof: ${runUrl(repository, bootstrapRunId)}`;
}
return {
verifierArgs,
proofLines: {
normal: normalProofLine,
bootstrap: bootstrapProofLine,
},
};
}
export function parseOpenClawReleaseClawHubPlanArgs(
argv: string[],
): OpenClawReleaseClawHubPlanArgs {
const values = [...argv];
if (values[0] === "--") {
values.shift();
}
let releaseTag: string | undefined;
let releasePublishBranch: string | undefined;
let releasePublishRunId: string | undefined;
let pluginPublishScope: PluginReleaseSelectionMode | undefined;
let plugins: string[] = [];
let pluginsFlagProvided = false;
for (let index = 0; index < values.length; index += 1) {
const arg = values[index];
const next = () => {
const value = values[index + 1];
if (value === undefined || value.startsWith("-")) {
throw new Error(`${arg} requires a value.`);
}
index += 1;
return value;
};
switch (arg) {
case "--release-tag":
releaseTag = next();
break;
case "--release-publish-branch":
releasePublishBranch = next();
break;
case "--release-publish-run-id":
releasePublishRunId = next();
break;
case "--plugin-publish-scope":
pluginPublishScope = parsePluginReleaseSelectionMode(next());
break;
case "--plugins":
plugins = parsePluginReleaseSelection(next());
pluginsFlagProvided = true;
break;
default:
throw new Error(`Unknown argument: ${arg}`);
}
}
const resolvedPluginPublishScope = pluginPublishScope ?? "all-publishable";
if (pluginsFlagProvided && plugins.length === 0) {
throw new Error("--plugins must include at least one package name.");
}
if (resolvedPluginPublishScope === "selected" && !pluginsFlagProvided) {
throw new Error("plugin-publish-scope=selected requires --plugins.");
}
if (resolvedPluginPublishScope === "all-publishable" && pluginsFlagProvided) {
throw new Error("plugin-publish-scope=all-publishable must not be combined with --plugins.");
}
return {
releaseTag: requireArg(releaseTag, "--release-tag"),
releasePublishBranch: requireArg(releasePublishBranch, "--release-publish-branch"),
releasePublishRunId: requireArg(releasePublishRunId, "--release-publish-run-id"),
pluginPublishScope: resolvedPluginPublishScope,
plugins,
};
}
export async function buildOpenClawReleaseClawHubPlan(
args: OpenClawReleaseClawHubPlanArgs,
options: {
rootDir?: string;
fetchImpl?: typeof fetch;
registryBaseUrl?: string;
} = {},
): Promise<OpenClawReleaseClawHubPlan> {
const releaseTag = requireArg(args.releaseTag, "releaseTag");
const releasePublishBranch = requireArg(args.releasePublishBranch, "releasePublishBranch");
const releasePublishRunId = requireArg(args.releasePublishRunId, "releasePublishRunId");
const plan = await collectPluginClawHubReleasePlan({
rootDir: options.rootDir ?? resolve("."),
selection: args.plugins,
selectionMode: args.pluginPublishScope,
fetchImpl: options.fetchImpl,
registryBaseUrl: options.registryBaseUrl,
});
const normalPackages = packageNames(plan.candidates);
const bootstrapPackages = [
...packageNames(plan.bootstrapCandidates),
...packageNames(plan.missingTrustedPublisher),
];
const missingTrustedPlugins = packageNames(plan.missingTrustedPublisher);
assertNoPackageOverlap(normalPackages, bootstrapPackages);
return {
clawHubWorkflowRef: releaseTag,
releasePublishBranch,
normal: createDispatchTarget({
workflow: "plugin-clawhub-release.yml",
ref: releaseTag,
packages: normalPackages,
releasePublishRunId,
releasePublishBranch,
includePublishScope: true,
}),
bootstrap: createDispatchTarget({
workflow: "plugin-clawhub-new.yml",
ref: releaseTag,
packages: bootstrapPackages,
releasePublishRunId,
releasePublishBranch,
includePublishScope: false,
}),
summary: {
normalCount: normalPackages.length,
bootstrapCount: bootstrapPackages.length,
missingTrustedPublisherCount: missingTrustedPlugins.length,
normalPlugins: joinPackageNames(normalPackages),
bootstrapPlugins: joinPackageNames(bootstrapPackages),
missingTrustedPlugins: joinPackageNames(missingTrustedPlugins),
},
verifier: {
clawHubWorkflowRef: releaseTag,
},
};
}
+156 -12
View File
@@ -60,15 +60,34 @@ type PluginReleasePlanItem = PublishablePluginPackage & {
type PluginReleasePlan = {
all: PluginReleasePlanItem[];
candidates: PluginReleasePlanItem[];
bootstrapCandidates: PluginReleasePlanItem[];
missingTrustedPublisher: PluginReleasePlanItem[];
skippedPublished: PluginReleasePlanItem[];
};
type ClawHubTrustedPublisherDetail = {
trustedPublisher?: unknown;
};
type ClawHubTrustedPublisherConfig = {
repository?: unknown;
workflowFilename?: unknown;
environment?: unknown;
};
type PluginReleasePlanItemWithPackageState = PluginReleasePlanItem & {
packageExists: boolean;
hasTrustedPublisher: boolean;
};
type ClawHubPublishablePluginPackageFilters = {
extensionIds?: readonly string[];
packageNames?: readonly string[];
};
const CLAWHUB_DEFAULT_REGISTRY = "https://clawhub.ai";
const OPENCLAW_PLUGIN_CLAWHUB_REPOSITORY = "openclaw/openclaw";
const OPENCLAW_PLUGIN_CLAWHUB_WORKFLOW_FILENAME = "plugin-clawhub-release.yml";
const SAFE_EXTENSION_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
const CLAWHUB_SHARED_RELEASE_INPUT_PATHS = [
".github/workflows/plugin-clawhub-release.yml",
@@ -357,6 +376,97 @@ async function isPluginVersionPublishedOnClawHub(
);
}
async function doesClawHubPackageExist(
packageName: string,
options: {
fetchImpl?: typeof fetch;
registryBaseUrl?: string;
} = {},
): Promise<boolean> {
const fetchImpl = options.fetchImpl ?? fetch;
const url = new URL(
`/api/v1/packages/${encodeURIComponent(packageName)}`,
getRegistryBaseUrl(options.registryBaseUrl),
);
const response = await fetchImpl(url, {
method: "GET",
headers: {
Accept: "application/json",
},
});
if (response.status === 404) {
return false;
}
if (!response.ok) {
throw new Error(
`Failed to query ClawHub package ${packageName}: ${response.status} ${response.statusText}`,
);
}
return true;
}
async function hasClawHubTrustedPublisher(
packageName: string,
options: {
fetchImpl?: typeof fetch;
registryBaseUrl?: string;
} = {},
): Promise<boolean> {
const fetchImpl = options.fetchImpl ?? fetch;
const url = new URL(
`/api/v1/packages/${encodeURIComponent(packageName)}/trusted-publisher`,
getRegistryBaseUrl(options.registryBaseUrl),
);
const response = await fetchImpl(url, {
method: "GET",
headers: {
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(
`Failed to query ClawHub trusted publisher for ${packageName}: ${response.status} ${response.statusText}`,
);
}
let trustedPublisherDetail: ClawHubTrustedPublisherDetail;
try {
trustedPublisherDetail = (await response.json()) as ClawHubTrustedPublisherDetail;
} catch (error) {
throw new Error(`Failed to parse ClawHub trusted publisher ${packageName} response.`, {
cause: error,
});
}
return isOpenClawPluginTrustedPublisher(trustedPublisherDetail.trustedPublisher);
}
function isOpenClawPluginTrustedPublisher(value: unknown): boolean {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return false;
}
const trustedPublisher = value as ClawHubTrustedPublisherConfig;
return (
trustedPublisher.repository === OPENCLAW_PLUGIN_CLAWHUB_REPOSITORY &&
trustedPublisher.workflowFilename === OPENCLAW_PLUGIN_CLAWHUB_WORKFLOW_FILENAME &&
trustedPublisher.environment == null
);
}
function stripPackageReleaseState(
item: PluginReleasePlanItemWithPackageState,
): PluginReleasePlanItem {
const {
packageExists: _packageExists,
hasTrustedPublisher: _hasTrustedPublisher,
...planItem
} = item;
return planItem;
}
export async function collectPluginClawHubReleasePlan(params?: {
rootDir?: string;
selection?: string[];
@@ -395,22 +505,56 @@ export async function collectPluginClawHubReleasePlan(params?: {
assertPluginReleaseVersionFloors(selectedPublishable, "Plugin ClawHub release plan");
}
const all = await Promise.all(
selectedPublishable.map(async (plugin) =>
Object.assign({}, plugin, {
alreadyPublished: await isPluginVersionPublishedOnClawHub(
plugin.packageName,
plugin.version,
{ registryBaseUrl: params?.registryBaseUrl, fetchImpl: params?.fetchImpl },
),
const planned = await Promise.all(
selectedPublishable.map(async (plugin): Promise<PluginReleasePlanItemWithPackageState> => {
const packageExists = await doesClawHubPackageExist(plugin.packageName, {
registryBaseUrl: params?.registryBaseUrl,
fetchImpl: params?.fetchImpl,
});
const hasTrustedPublisher = packageExists
? await hasClawHubTrustedPublisher(plugin.packageName, {
registryBaseUrl: params?.registryBaseUrl,
fetchImpl: params?.fetchImpl,
})
: false;
const alreadyPublished = packageExists
? await isPluginVersionPublishedOnClawHub(plugin.packageName, plugin.version, {
registryBaseUrl: params?.registryBaseUrl,
fetchImpl: params?.fetchImpl,
})
: false;
return {
extensionId: plugin.extensionId,
packageDir: plugin.packageDir,
packageName: plugin.packageName,
version: plugin.version,
channel: plugin.channel,
publishTag: plugin.publishTag,
packageExists,
hasTrustedPublisher,
alreadyPublished,
artifactName: formatClawHubPackageArtifactName(plugin),
}),
),
};
}),
);
const all = planned.map(stripPackageReleaseState);
return {
all,
candidates: all.filter((plugin) => !plugin.alreadyPublished),
skippedPublished: all.filter((plugin) => plugin.alreadyPublished),
candidates: planned
.filter(
(plugin) => plugin.packageExists && plugin.hasTrustedPublisher && !plugin.alreadyPublished,
)
.map(stripPackageReleaseState),
bootstrapCandidates: planned
.filter((plugin) => !plugin.packageExists)
.map(stripPackageReleaseState),
missingTrustedPublisher: planned
.filter((plugin) => plugin.packageExists && !plugin.hasTrustedPublisher)
.map(stripPackageReleaseState),
skippedPublished: planned
.filter((plugin) => plugin.alreadyPublished)
.map(stripPackageReleaseState),
};
}
+25 -2
View File
@@ -18,6 +18,7 @@ export type ReleaseVerifyBetaArgs = {
repo: string;
registry: string;
workflowRef?: string;
clawHubWorkflowRef?: string;
pluginSelection: string[];
evidenceOut?: string;
skipPostpublish: boolean;
@@ -29,6 +30,7 @@ export type ReleaseVerifyBetaArgs = {
openclawNpm?: string;
pluginNpm?: string;
pluginClawHub?: string;
pluginClawHubBootstrap?: string;
npmTelegram?: string;
};
};
@@ -119,7 +121,7 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
const version = values.shift();
if (!version || version.startsWith("-")) {
throw new Error(
"Usage: pnpm release:verify-beta -- <version> [--workflow-ref REF] [--full-release-validation-run ID] [--openclaw-npm-run ID] [--plugin-npm-run ID] [--plugin-clawhub-run ID] [--npm-telegram-run ID] [--skip-github-release] [--skip-clawhub]",
"Usage: pnpm release:verify-beta -- <version> [--workflow-ref REF] [--clawhub-workflow-ref REF] [--full-release-validation-run ID] [--openclaw-npm-run ID] [--plugin-npm-run ID] [--plugin-clawhub-run ID] [--plugin-clawhub-bootstrap-run ID] [--npm-telegram-run ID] [--skip-github-release] [--skip-clawhub]",
);
}
@@ -130,6 +132,7 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
repo: DEFAULT_REPO,
registry: DEFAULT_CLAWHUB_REGISTRY,
workflowRef: undefined,
clawHubWorkflowRef: undefined,
pluginSelection: [],
evidenceOut: undefined,
skipPostpublish: false,
@@ -166,6 +169,9 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
case "--workflow-ref":
parsed.workflowRef = next();
break;
case "--clawhub-workflow-ref":
parsed.clawHubWorkflowRef = next();
break;
case "--plugins":
parsed.pluginSelection = parsePluginReleaseSelection(next());
if (parsed.pluginSelection.length === 0) {
@@ -187,6 +193,9 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
case "--plugin-clawhub-run":
parsed.workflowRuns.pluginClawHub = next();
break;
case "--plugin-clawhub-bootstrap-run":
parsed.workflowRuns.pluginClawHubBootstrap = next();
break;
case "--npm-telegram-run":
parsed.workflowRuns.npmTelegram = next();
break;
@@ -567,17 +576,31 @@ export async function verifyBetaRelease(
);
}
if (args.workflowRuns.pluginClawHub !== undefined) {
const clawHubWorkflowRef = args.clawHubWorkflowRef ?? args.workflowRef;
workflowRuns.push(
verifyWorkflowRun({
id: args.workflowRuns.pluginClawHub,
label: "Plugin ClawHub Release",
repo: args.repo,
expectedWorkflowName: "Plugin ClawHub Release",
expectedHeadBranch: args.workflowRef,
expectedHeadBranch: clawHubWorkflowRef,
rerunFailed: args.rerunFailedClawHub,
}),
);
}
if (args.workflowRuns.pluginClawHubBootstrap !== undefined) {
const clawHubWorkflowRef = args.clawHubWorkflowRef ?? args.workflowRef;
workflowRuns.push(
verifyWorkflowRun({
id: args.workflowRuns.pluginClawHubBootstrap,
label: "Plugin ClawHub New",
repo: args.repo,
expectedWorkflowName: "Plugin ClawHub New",
expectedHeadBranch: clawHubWorkflowRef,
rerunFailed: false,
}),
);
}
if (args.workflowRuns.openclawNpm !== undefined) {
workflowRuns.push(
verifyWorkflowRun({
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env -S node --import tsx
// OpenClaw release ClawHub plan CLI emits release workflow routing as JSON.
import { pathToFileURL } from "node:url";
import {
buildOpenClawReleaseClawHubPlan,
parseOpenClawReleaseClawHubPlanArgs,
} from "./lib/openclaw-release-clawhub-plan.ts";
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
const args = parseOpenClawReleaseClawHubPlanArgs(process.argv.slice(2));
const plan = await buildOpenClawReleaseClawHubPlan(args);
console.log(JSON.stringify(plan, null, 2));
}
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env -S node --import tsx
import { buildOpenClawReleaseClawHubRuntimeState } from "./lib/openclaw-release-clawhub-plan.ts";
function parseBoolean(value: string, label: string): boolean {
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
throw new Error(`${label} must be true or false.`);
}
function parseArgs(argv: string[]) {
const values = [...argv];
if (values[0] === "--") {
values.shift();
}
let repository: string | undefined;
let waitForClawHub: boolean | undefined;
let forceSkipClawHub: boolean | undefined;
let normalRunId: string | undefined;
let bootstrapRunId: string | undefined;
let bootstrapCompleted: boolean | undefined;
for (let index = 0; index < values.length; index += 1) {
const arg = values[index];
const next = () => {
const value = values[index + 1];
if (value === undefined || value.startsWith("-")) {
throw new Error(`${arg} requires a value.`);
}
index += 1;
return value;
};
switch (arg) {
case "--repository":
repository = next();
break;
case "--wait-for-clawhub":
waitForClawHub = parseBoolean(next(), "--wait-for-clawhub");
break;
case "--force-skip-clawhub":
forceSkipClawHub = parseBoolean(next(), "--force-skip-clawhub");
break;
case "--normal-run-id":
normalRunId = next();
break;
case "--bootstrap-run-id":
bootstrapRunId = next();
break;
case "--bootstrap-completed":
bootstrapCompleted = parseBoolean(next(), "--bootstrap-completed");
break;
default:
throw new Error(`Unknown argument: ${arg}`);
}
}
if (!repository?.trim()) {
throw new Error("--repository is required.");
}
if (waitForClawHub === undefined) {
throw new Error("--wait-for-clawhub is required.");
}
if (forceSkipClawHub === undefined) {
throw new Error("--force-skip-clawhub is required.");
}
if (bootstrapCompleted === undefined) {
throw new Error("--bootstrap-completed is required.");
}
return {
repository,
waitForClawHub,
forceSkipClawHub,
normalRunId,
bootstrapRunId,
bootstrapCompleted,
};
}
try {
const state = buildOpenClawReleaseClawHubRuntimeState(parseArgs(process.argv.slice(2)));
process.stdout.write(`${JSON.stringify(state, null, 2)}\n`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(message);
process.exit(1);
}
+8
View File
@@ -42,6 +42,7 @@ source_repo="${SOURCE_REPO:-${GITHUB_REPOSITORY:-openclaw/openclaw}}"
source_commit="${SOURCE_COMMIT:-$(git -C "${invocation_root}" rev-parse HEAD)}"
source_ref="${SOURCE_REF:-$(git -C "${invocation_root}" symbolic-ref -q HEAD || true)}"
clawhub_workdir="${CLAWDHUB_WORKDIR:-${CLAWHUB_WORKDIR:-${invocation_root}}}"
manual_override_reason="${OPENCLAW_CLAWHUB_MANUAL_OVERRIDE_REASON:-}"
pack_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-clawhub-pack.XXXXXX")"
cleanup() {
@@ -158,6 +159,13 @@ if [[ -n "${source_ref}" ]]; then
)
fi
if [[ -n "${manual_override_reason}" ]]; then
publish_cmd+=(
--manual-override-reason
"${manual_override_reason}"
)
fi
printf 'Publish command: CLAWHUB_WORKDIR=%q' "${clawhub_workdir}"
printf ' %q' "${publish_cmd[@]}"
printf '\n'