tldraw/tldraw · error · Error

・ERROR・ Cannot cherry-pick docs changes from PR '${pr.title}

Error message

・ERROR・
Cannot cherry-pick docs changes from PR '${pr.title}' https://github.com/tldraw/tldraw/pulls/${pr}
This PR contains changes to the SDK.

${formattedDiff}
💡 Please cherry-pick this PR manually and remove the sdk changes.
   Or apply the sdk-hotfix-please PR label and rerun this action to publish the sdk changes if appropriate.

What it means

Thrown by the SDK hotfix orchestrator when a PR carries the 'docs-hotfix-please' label (intended for docs-only cherry-picks) but the diff check (getAnyPackageDiff) detects changes to SDK package contents. The script refuses to silently ship SDK code changes under a docs-only hotfix and asks for manual intervention or the sdk-hotfix-please label instead. The message includes a truncated diff for context.

Source

Thrown at internal/scripts/trigger-sdk-hotfix.ts:128

			await exec('yarn', ['refresh-assets', '--force'])

			const diff = await getAnyPackageDiff(version.format())
			if (diff) {
				let message = kleur.red().bold(`・ERROR・`)
				message += `\nCannot cherry-pick docs changes from PR '${kleur.cyan().bold(pr.title)}' https://github.com/tldraw/tldraw/pulls/${pr}`
				message += '\nThis PR contains changes to the SDK.\n\n'
				let formattedDiff = formatDiff(diff)
				// truncate long diffs for discord's sake, it's not that valuable to see the whole thing
				if (formattedDiff.length > 1000) {
					formattedDiff = formattedDiff.slice(0, 1000) + '\n...\n'
				}
				message += formattedDiff

				message += '\n💡 Please cherry-pick this PR manually and remove the sdk changes.'
				message +=
					'\n   Or apply the sdk-hotfix-please PR label and rerun this action to publish the sdk changes if appropriate.'

				throw new Error(message)
			}
		})
	} else {
		await discord.step('Running sdk tests', async () => {
			await exec('yarn', ['install'])
			const packages = await getAllWorkspacePackages()

			await exec('yarn', [
				'test',
				...packages
					.filter((p) => !p.packageJson.private)
					.flatMap(({ name }) => ['--project', name]),
			])
		})
	}

	await discord.step('Pushing to release branch', async () => {
		await exec('git', ['push', 'origin', `HEAD:${latestReleaseBranch}`])

View on GitHub (pinned to b31086b447)

Solutions

  1. Cherry-pick the PR manually onto the release branch with the SDK changes removed, keeping only docs files.
  2. If the SDK changes are intentional and should ship, add the 'sdk-hotfix-please' label and rerun the action.
  3. Split the PR into two: one docs-only PR (docs-hotfix-please) and one SDK PR (sdk-hotfix-please).
  4. Inspect the formatted diff in the error message to identify which package files triggered the guard.

Example fix

// before
if (diff) {
  // ... build message ...
  throw new Error(message)
}

// after: also expose which packages changed for faster triage
if (diff) {
  const changedPackages = Object.keys(diff).join(', ')
  throw new Error(
    `${message}\nChanged packages: ${changedPackages}`
  )
}
Defensive patterns

Strategy: validation

Validate before calling

// Before labeling docs-hotfix-please, confirm the PR has no SDK package diff
import { getAnyPackageDiff } from './lib/didAnyPackageChange'

const diff = await getAnyPackageDiff(latestVersion)
if (diff) {
  throw new Error(
    'PR has SDK changes; use sdk-hotfix-please instead of docs-hotfix-please, or split the PR.'
  )
}

Prevention

When it happens

Trigger: A PR labeled docs-hotfix-please touches files under packages/* that change the built output or source of a published package. getAnyPackageDiff against the latest version returns a non-empty diff, triggering the guard.

Common situations: A PR was mislabeled (docs-only intent but SDK files changed). A docs change incidentally modified a re-exported type or asset that counts as an SDK diff. The label was applied before the SDK change was added.

Related errors


AI-assisted analysis of tldraw/tldraw@b31086b447 (2026-08-12). Data as JSON: /api/errors/91a1b044d1208f2d. Report an issue: GitHub.