gitbutlerapp/gitbutler · warning

Failed to migrate GitLab token for project ${projectId}:

Error message

Failed to migrate GitLab token for project ${projectId}:

What it means

migrate() lifts a project's GitLab PAT out of the legacy per-project secret key (git-lab-token:{projectId} in the OS secret store) into the backend via the storeGitLabPat endpoint, then deletes the old key. Any failure in that chain is caught and warned so migration never breaks startup. The store-before-delete ordering makes the operation idempotent: a failed run leaves the old key in place and later retries converge.

Source

Thrown at apps/desktop/src/lib/forge/gitlab/gitlabUserService.svelte.ts:115

	constructor(
		backendApi: BackendApi,
		private secretsService: SecretsService,
	) {
		this.backendApi = injectBackendEndpoints(backendApi);
	}

	/**
	 * Migrate the access token for the given project from the old storage location (if it exists) to the new one.
	 */
	async migrate(projectId: string): Promise<void> {
		try {
			const gitlabToken = await this.secretsService.get(`git-lab-token:${projectId}`);
			if (!gitlabToken) return;
			await this.backendApi.endpoints.storeGitLabPat.initiate({ accessToken: gitlabToken });
			await this.secretsService.delete(`git-lab-token:${projectId}`);
		} catch (error) {
			// Fail should not explote. Log instead.
			console.warn(`Failed to migrate GitLab token for project ${projectId}:`, error);
		}
	}

	get storeGitLabPat() {
		return this.backendApi.endpoints.storeGitLabPat.useMutation();
	}

	get storeGitLabEnterprisePat() {
		return this.backendApi.endpoints.storeGitLabEnterprisePat.useMutation();
	}

	get forgetGitLabAccount() {
		return this.backendApi.endpoints.forgetGitLabAccount.useMutation();
	}

	authenticatedUser<T = GitlabAuthenticatedUserSensitive | null>(
		account: GitlabAccountIdentifier,
		options?: { transform?: (result: GitlabAuthenticatedUserSensitive | null) => T },

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Do nothing — the next launch re-runs migrate() and converges because the old key is deleted only after a successful store
  2. If the backend rejects the token, re-enter the PAT in settings so the new store receives a valid one
  3. If the secret store errors, unlock the keychain or verify libsecret on Linux, then restart
  4. If stale duplicates cause issues, manually clear the old git-lab-token:{projectId} secret
Defensive patterns

Strategy: retry

Validate before calling

// Only touch the backend when there is actually something to migrate
const token = await this.secretsService.get(`git-lab-token:${projectId}`);
if (!token) return;

Type guard

function isKeychainError(error: unknown): boolean {
	return error instanceof Error && /keychain|secret|access denied/i.test(error.message);
}

Try / catch

try {
	await this.backendApi.endpoints.storeGitLabPat.initiate({ accessToken: token });
	await this.secretsService.delete(`git-lab-token:${projectId}`);
} catch (error) {
	console.warn(`Failed to migrate GitLab token for ${projectId}:`, error);
	// safe to retry next launch: the old key still exists
}

Prevention

When it happens

Trigger: Calling migrate(projectId) when secretsService.get or .delete throws (secret store unavailable, keychain locked) or when the backendApi storeGitLabPat mutation rejects (backend down, auth failure, token rejected).

Common situations: Backend not ready when migration runs at app start; macOS keychain access denied; GitLab token revoked server-side; a crash between store and delete leaving copies in both places.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/67993a3a0757c844. Report an issue: GitHub.