laurent22/joplin · error · Error

You are in a detached HEAD state. Checkout a branch (e.g. gi

Error message

You are in a detached HEAD state. Checkout a branch (e.g. git checkout main) and push before publishing.

What it means

Thrown by verifyGitState when `git rev-parse --abbrev-ref HEAD` returns the literal string 'HEAD', which is git's signal that the repository is in detached HEAD state (HEAD points directly at a commit SHA rather than a branch ref). Publishing from a detached HEAD is blocked because there is no branch to push and to sync against the remote.

Source

Thrown at packages/generator-joplin/generators/app/templates/script/publish/steps/verifyGitState.ts:44

	const status = await runGit('git status --porcelain', 'Failed to check git status. Ensure git is installed and configured.');
	if (status !== '') {
		throw new Error('You have uncommitted changes. Please commit or stash them before publishing.');
	}
	logger.success('Working tree is clean.');

	// Gets the current latest local commit hash
	const commitHash = await runGit('git rev-parse HEAD', 'Could not get commit hash. Ensure you are in a valid Git repository with at least one commit.');
	if (commitHash.length !== 40) {
		throw new Error('Failed to extract a valid commit hash. Ensure that git is properly initialized and you have made at least one local commit (git commit) before publishing.');
	}
	logger.success(`Commit hash extracted: ${commitHash}`);

	// check if the local project is linked to github
	await runGit('git remote get-url origin', 'No remote named \'origin\' found. Make sure your plugin repository is hosted on GitHub.');

	const currentBranch = await runGit('git rev-parse --abbrev-ref HEAD', 'Failed to retrieve current branch name. Ensure git is configured correctly.');
	if (currentBranch === 'HEAD') {
		throw new Error('You are in a detached HEAD state. Checkout a branch (e.g. git checkout main) and push before publishing.');
	}

	const remoteHeadLine = await runGit(`git ls-remote origin ${currentBranch}`, 'Could not retrieve remote HEAD. Make sure you have pushed your changes and have an internet connection.');
	if (!remoteHeadLine) {
		throw new Error('Remote HEAD is empty. Make sure you have pushed your changes.');
	}

	const parts = remoteHeadLine.split('\n')[0].split(/\s+/);
	if (parts.length < 2) {
		throw new Error(`Unexpected git ls-remote output: "${remoteHeadLine}". Make sure your git remote and branch are configured correctly.`);
	}

	const remoteHash = parts[0];
	if (remoteHash.length !== 40) {
		throw new Error('Failed to extract a valid remote commit hash.');
	}

	if (remoteHash !== commitHash) {

View on GitHub (pinned to 2654b33620)

Solutions

  1. Checkout a branch: git checkout main (or master), then git push.
  2. In CI, configure the checkout to persist on a branch, or create a temporary branch: git switch -c publish-branch before publishing.
  3. If mid-rebase, complete or abort the rebase first.

Example fix

# before
git checkout 1a2b3c4
 npm run publish
# after
git checkout main && git push
 npm run publish
Defensive patterns

Strategy: validation

Validate before calling

const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
if (branch === 'HEAD') {
  throw new Error('Detached HEAD. Run: git checkout main');
}

Type guard

function isOnBranch(b) { return b !== 'HEAD' && b.length > 0; }

Prevention

When it happens

Trigger: The user checked out a specific commit SHA: git checkout <sha>; a rebase in progress left HEAD detached; CI checked out a ref that resolved to a detached HEAD (e.g. actions/checkout without a branch); the repo was cloned then a tag was checked out.

Common situations: CI pipeline using actions/checkout@v2 which defaults to detached HEAD; developer inspecting an old commit then running publish; post-rebase before re-checking out the branch; checking out a tag for a release.

Related errors


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/03848c21809bdec3. Report an issue: GitHub.