git-integration-operations-cli

Automate the Microsoft Fabric Git integration lifecycle from CLI environments using the Fabric CLI (fab api), with az rest as a fallback. Use when the user…

npx skills add https://github.com/microsoft/skills-for-fabric --skill git-integration-operations-cli

Telemetry — MANDATORY. Every api.fabric.microsoft.com call must carry x-ms-fabric-skill: git-integration-operations-cli (az rest: --headers "x-ms-fabric-skill=git-integration-operations-cli"), including every LRO poll, fabric_lro and retry. Snippets omit it — add it anyway.

CRITICAL NOTES

  1. To find a workspace ID from its name: list all workspaces, then filter by displayName with JMESPath. To find an item ID: list items of that type in the workspace, then filter by displayName.
  2. All Git operations are control plane calls against https://api.fabric.microsoft.com. Run fab auth login once and fab api sets the base URL and Fabric token audience automatically. With the az rest fallback you must pass the matching --resource yourself, or you get a 401.
  3. commitToGit, updateFromGit, and even git/status can return HTTP 202 (long-running operation), not just a synchronous body. On 202, poll the operation: with fab, read x-ms-operation-id from --show_headers and poll fab api operations/{id} until status is Succeeded; with the az rest fallback, poll the Location / x-ms-operation-id header (see COMMON-CORE long-running operation polling). THEN read git/status to confirm workspaceHead == remoteCommitHash. Prefer the operation poll to confirm completion; if you poll git/status instead, treat an in-progress (202) or not-yet-synced response as "still running" and do not act on its changes until the heads match. (Learn: rest/api/fabric/core/git and fabric/cicd/git-integration/git-automation.)
  4. updateFromGit requires the workspace's current workspaceHead; a stale value returns 400 WorkspaceHeadMismatch. Always read git/status first.
  5. Prerequisites for every Git operation: the workspace must be assigned to a capacity (an unassigned workspace fails with WorkspaceHasNoCapacityAssigned), and the caller (user or service principal) must hold the right workspace role: connect and disconnect require Admin; commit and update require at least Contributor with write permission on all items; branch switching requires Admin (or Contributor when the workspace opt-in setting Allow users with at least Contributor role to change Git branch is on). Only one Git operation may run against a workspace at a time.
  6. Tenant admin switches gate Git integration and are a common cause of an unexplained failure. The GitHub sync switch is off by default (enable it before any GitHub connect); Azure DevOps sync is on by default. Cross-region workspace-to-Azure-repo needs the cross-geo export switch (GitHub does not enforce it). See references/git-integration-concepts.md § Tenant admin prerequisites.

Git Integration Operations — CLI Skill

Automate the Fabric Git integration lifecycle (connect, commit, update, status, disconnect) from CLI environments. Whether cross-workspace item references rebind after promotion is a separate concern, determined by the item definition format (logical vs object IDs) rather than the Git lifecycle itself.

Prerequisite Knowledge

Read these shared references first (paths assume this skill lives under skills/git-integration-operations-cli/ in skills-for-fabric; adjust when drafting outside the repo):

Table of Contents

TaskReference
Pre-flight checks before connectingSKILL.md § Pre-Flight (before you connect)
Connect a workspace to GitSKILL.md § Connect a Workspace to Git
Create a git connection (service principal)SKILL.md § Create the Git provider connection (service principal)
Commit workspace items to GitSKILL.md § Commit to Git
Update a workspace from GitSKILL.md § Update from Git
Check sync statusSKILL.md § Check Sync Status
Resolve update conflictsSKILL.md § Resolve Conflicts
Disconnect from GitSKILL.md § Disconnect from Git
Link a branch workspace to its base (workspace relations)SKILL.md § Link a Branch Workspace to its Base (Workspace Relations)
Git integration concepts (sync model, status, permissions)references/git-integration-concepts.md § Concepts
Tenant admin prerequisites (switches)references/git-integration-concepts.md § Tenant admin prerequisites
Supported Git providersreferences/git-integration-concepts.md § Supported Git providers
Supported item typesreferences/git-integration-concepts.md § Supported item types
Avoiding formatting-only diffs (trailing newline / line endings)references/git-integration-concepts.md § Avoiding formatting-only diffs
Service principal / CI-CD pipeline templatesreferences/automation-templates.md
Gotchas, Rules, TroubleshootingSKILL.md § Gotchas, Rules, Troubleshooting

Must/Prefer/Avoid

MUST DO

  • Read git/status and pass the current workspaceHead into every commitToGit and updateFromGit call.
  • Poll git/status after each async operation until workspaceHead == remoteCommitHash and the changes array is empty. A 202/Succeeded status is not proof of a correct sync — validate the state.
  • Deploy an item together with the items it references in the same commit, so logical IDs can resolve inside the target workspace.

MUST NEVER

  • Never run two Git operations concurrently on the same workspace — a second commitToGit/updateFromGit while one is still Running corrupts head tracking. Serialize: poll the first to Succeeded before starting the next.
  • Never treat a workspace as both a source of truth and a CI/CD push target on the same branch. Pick one writer per branch: either humans commit from the workspace, or automation pushes to Git — not both at once.

PREFER

  • fab api (after fab auth login) as the primary driver: it sets the base URL and Fabric token audience automatically, avoiding the 401 wrong-audience trap. Use az rest with --resource "https://api.fabric.microsoft.com" as the fallback when fab is unavailable or you need fine-grained header control.
  • PreferRemote conflict resolution for a clean "pull latest from Git" into a downstream/target workspace.

AVOID

  • Reusing a stale workspaceHead (causes 400 WorkspaceHeadMismatch).
  • Passing request bodies via stdin to fab api (use -i <file.json> or -i '<inline JSON>').

CLI: fab primary, az rest fallback

Every operation below is shown with fab api. Run fab auth login once (it supports service principals for automation); fab then handles the base URL and token audience for you. The identical call on the az rest fallback is a mechanical translation:

Authenticate fab for the identity you are running as:

fab auth login                                              # interactive user (SSO)
fab auth login -u <client-id> -p <client-secret> --tenant <tenant-id>   # service principal (CI/CD)
fab auth login --identity                                   # managed identity (Azure compute)

A service principal or managed identity must be workspace Admin to connect or disconnect (at least Contributor with write on all items is enough for commit/update) and (for the ConfiguredConnection path) hold the git provider credentials. See references/automation-templates.md for full pipeline scripts.

  • fab api <endpoint> becomes az rest --method GET --url "https://api.fabric.microsoft.com/v1/<endpoint>" --resource "https://api.fabric.microsoft.com".
  • fab api -X post <endpoint> -i body.json adds --method POST --headers "Content-Type=application/json" --body @body.json.

fab api reads a request body via -i from either a file path or an inline JSON string (e.g. -i '{"displayName":"..."}') — never from stdin. It prints {"status_code": <code>, "text": <parsed body>} (add --show_headers for a top-level headers object), so read response fields with jq -r '.text.<field>' and the LRO id with jq -r '.headers["x-ms-operation-id"]'. See Example 2 for a full az rest pair.


Pre-Flight (before you connect)

Run these Git-relevant checks in order before git/connect. Each failure has a specific fix — resolving them up front avoids the most common connect-time errors.

CheckCommandPass = proceed / Fail = fix
Auth + Fabric tokenfab auth login then fab api workspaces401 → re-login (fab sets the correct audience automatically)
Caller is workspace Adminfab api "workspaces/${WORKSPACE_ID}/roleAssignments"Connect/disconnect require the Admin role
Workspace has capacityfab api "workspaces/${WORKSPACE_ID}" → check capacityIdWorkspaceHasNoCapacityAssigned → assign a capacity (Git requires one)
Git credential for the SP pathfab api connections (reuse if one already fits)SP connect needs a ConfiguredConnection. If none exists, the skill creates it from your ADO org/project/repo + SP tenant/client/secret — see Create the Git provider connection (needs the tenant toggle "service principals can create connections"). The interactive-user SSO path uses Automatic and needs no connection.
Target directoryName exists on the branchGitHub: curl -fsS -H "Authorization: Bearer $PAT" "https://api.github.com/repos/$OWNER/$REPO/contents/$DIR?ref=$BRANCH" — ADO: Items API on the repoThe API (unlike the portal) does not create a missing folder — connecting to a directoryName that doesn't exist fails with 404 GitProviderResourceNotFound. If the folder is new, pre-create it first (see the note under Connect a Workspace to Git).

Connect a Workspace to Git

Connect a workspace to an Azure DevOps or GitHub repo, then initialize the connection to establish the sync direction.

# 1. Connect (Azure DevOps example). Body goes in a file, passed with -i.
cat > connect.json <<'JSON'
{
  "gitProviderDetails": {
    "gitProviderType": "AzureDevOps",
    "organizationName": "myorg",
    "projectName": "MyProject",
    "repositoryName": "fabric",
    "branchName": "main",
    "directoryName": "/workspace-a"
  }
}
JSON
fab api -X post "workspaces/${WORKSPACE_ID}/git/connect" -i connect.json

# 2. Initialize the connection (choose which side wins on first sync)
printf '{"initializationStrategy":"PreferWorkspace"}' > init.json
fab api -X post "workspaces/${WORKSPACE_ID}/git/initializeConnection" -i init.json

Use PreferWorkspace when the workspace already has the authoritative items; PreferRemote when the Git branch is authoritative.

The target directoryName must already exist on the branch. Unlike the Fabric portal — which silently creates a missing folder by committing a README.md placeholder into it — the git/connect API does not create the folder. Connecting to a directoryName that does not exist on the branch fails with 404 GitProviderResourceNotFound. The repo and branch must exist too (connect never creates a branch). If the folder is new, create it first by committing a placeholder file to the branch through the Git provider's API (not a Fabric API), then connect. Match the portal's convention with a README.md whose body is This is an auto-created file via Fabric skills for <workspace-name>.

# GitHub: create <DIR>/README.md on <BRANCH> (reuse the PAT behind the Git connection)
DIR="workspace-a"; WS_NAME="My Workspace"
CONTENT=$(printf 'This is an auto-created file via Fabric skills for %s\n' "$WS_NAME" | base64)
cat > mkfolder.json <<JSON
{ "message": "Create ${DIR} folder for Fabric Git connect", "branch": "${BRANCH}", "content": "${CONTENT}" }
JSON
curl -fsS -X PUT -H "Authorization: Bearer ${GITHUB_PAT}" \
  "https://api.github.com/repos/${OWNER}/${REPO}/contents/${DIR}/README.md" -d @mkfolder.json
# Azure DevOps equivalent: POST .../_apis/git/repositories/${REPO}/pushes with an
# "add" change for "/${DIR}/README.md" on refs/heads/${BRANCH} (same body content).

Two authentication modes (both providers use myGitCredentials for automation). A workspace authenticates to git in one of two ways:

  • Automatic git credential (Azure DevOps only): the connecting user's SSO identity, established from workspace settings. Secondary contributors with at least Contributor reuse it. Not available for GitHub, and not usable for headless / service-principal automation.
  • Configured credential (Azure DevOps and GitHub): a Fabric cloud connection referenced in the connect body. This is the required path for any service principal or unattended run:
    "myGitCredentials": { "source": "ConfiguredConnection", "connectionId": "<connection-guid>" }
    
    For Azure DevOps, the connection's credentialType can be either ServicePrincipal (headless automation) or OAuth2 (a stored delegated user token); both are valid ConfiguredConnection credentials per the Create Connection API and both support multitenant scenarios. For GitHub, connecting a workspace requires a Personal Access Token (PAT): the cloud connection stores a fine-grained token with Contents Read (plus Write to commit), or a classic token with the repo scope. GitHub has no automatic credential mode, so a PAT is mandatory even for an interactive user connect. The distinction is not "GitHub needs credentials, ADO does not". ADO also requires a ConfiguredConnection whenever a service principal drives the connect; the automatic mode only covers interactive user SSO. (Learn: fabric/cicd/git-integration/automate-git-integration-with-service-principal; rest/api/fabric/core/git/connect.)

Handle the initialize response. initializeConnection returns requiredAction (UpdateFromGit, CommitToGit, or None) plus workspaceHead and remoteCommitHash, and can itself return 202. After it settles, call the sync operation named by requiredAction with the returned hashes, or the workspace stays connected but not fully synced. (Learn: rest/api/fabric/core/git/initialize-connection.)

Create the Git provider connection (service principal)

For unattended automation, first create a Fabric cloud connection that stores the git provider credentials, then pass its id as the connectionId in the myGitCredentials block above. Use the Create Connection API (POST /v1/connections, delegated scope Connection.ReadWrite.All). A service principal can create the connection only if the Fabric admin setting "service principals can create workspaces, connections, and deployment pipelines" is on.

Information you must supply (Azure DevOps):

  • ADO organization, project, and repository names, combined into one url parameter: https://dev.azure.com/<org>/<project>/_git/<repo>/
  • The service principal's tenant ID, client (application) ID, and client secret
  • A connection display name (max 200 chars, must be unique in the tenant)

Prerequisites before the connection will work: grant the service principal (a) access to that Azure DevOps organization and project (git access is enforced by ADO itself, not only by Fabric), and (b) Admin on the target Fabric workspace.

# Create an Azure DevOps source-control connection using a service principal.
# The body holds a cleartext SP secret, so restrict perms and delete it on exit
# (a leftover connection.json can be captured by a later CI step or artifact upload).
trap 'rm -f connection.json' EXIT
umask 077
cat > connection.json <<'JSON'
{
  "displayName": "ado-sp-fabric-cicd",
  "connectivityType": "ShareableCloud",
  "connectionDetails": {
    "type": "AzureDevOpsSourceControl",
    "creationMethod": "AzureDevOpsSourceControl.Contents",
    "parameters": [
      { "dataType": "Text", "name": "url",
        "value": "https://dev.azure.com/<org>/<project>/_git/<repo>/" }
    ]
  },
  "credentialDetails": {
    "credentials": {
      "credentialType": "ServicePrincipal",
      "tenantId": "<tenant-id>",
      "servicePrincipalClientId": "<client-id>",
      "servicePrincipalSecret": "<client-secret>"
    }
  }
}
JSON
fab api -X post "connections" -i connection.json

The 201 response returns the connection id; use it as connectionId in the myGitCredentials block of the connect call. For a fully secret-free variant, pass the body inline with -i '{...}' (no file on disk) or reference a Key Vault secret via servicePrincipalSecretReference instead of the raw servicePrincipalSecret. The example above uses credentialType: "ServicePrincipal"; Azure DevOps connections also accept credentialType: "OAuth2" (a stored delegated user token), and both credential types support multitenant scenarios. To switch an existing user-connected workspace to a service principal, add the SP as workspace Admin, share or recreate this connection, then PATCH /workspaces/{id}/git/myGitCredentials with the same ConfiguredConnection body. (Learn: fabric/cicd/git-integration/automate-git-integration-with-service-principal; rest/api/fabric/core/connections/create-connection.)

Commit to Git

Push workspace items to the connected branch. Read the head first, then commit.

# Read current head. git/status can return 202 (LRO) with no body — capture the
# status code with --show_headers and poll before reading, or workspaceHead comes
# back as "null" and the commit below fails with a misleading 400.
RESP=$(fab api "workspaces/${WORKSPACE_ID}/git/status" --show_headers)
if [ "$(echo "$RESP" | jq -r '.status_code')" = "202" ]; then
  OP=$(echo "$RESP" | jq -r '.headers["x-ms-operation-id"]')
  until [ "$(fab api "operations/${OP}" | jq -r '.text.status')" = "Succeeded" ]; do sleep 5; done
  RESP=$(fab api "workspaces/${WORKSPACE_ID}/git/status" --show_headers)   # now 200 with body
fi
HEAD=$(echo "$RESP" | jq -r '.text.workspaceHead')

# Commit all pending changes. Build the body with jq so the comment is always
# valid JSON even if it contains a quote, backslash, or newline.
jq -n --arg head "$HEAD" --arg msg "Author items" \
  '{mode:"All", workspaceHead:$head, comment:$msg}' > commit.json
RESP=$(fab api -X post "workspaces/${WORKSPACE_ID}/git/commitToGit" -i commit.json --show_headers)
# commitToGit may complete synchronously (200) or async (202). Only poll when async.
if [ "$(echo "$RESP" | jq -r '.status_code')" = "202" ]; then
  OP=$(echo "$RESP" | jq -r '.headers["x-ms-operation-id"]')
  until [ "$(fab api "operations/${OP}" | jq -r '.text.status')" = "Succeeded" ]; do sleep 5; done
fi

Use "mode": "Selective" with an items array to commit a subset. commitToGit completes either synchronously (200, already settled) or asynchronously (202 with an x-ms-operation-id to poll) — branch on .status_code as above rather than assuming 202.

Update from Git

Pull committed items into a workspace (for example, a downstream or target workspace connected to the same branch/folder).

# Read both hashes from the target workspace
STATUS=$(fab api "workspaces/${TARGET_WS}/git/status")
TH=$(echo "$STATUS" | jq -r '.text.workspaceHead')
REMOTE=$(echo "$STATUS" | jq -r '.text.remoteCommitHash')

cat > update.json <<JSON
{
  "workspaceHead": "${TH}",
  "remoteCommitHash": "${REMOTE}",
  "conflictResolution": {
    "conflictResolutionType": "Workspace",
    "conflictResolutionPolicy": "PreferRemote"
  },
  "options": { "allowOverrideItems": true }
}
JSON
fab api -X post "workspaces/${TARGET_WS}/git/updateFromGit" -i update.json

Check Sync Status

Poll until the workspace and remote heads match. Both commitToGit and updateFromGit are asynchronous, so this loop is how you know they finished.

for i in $(seq 1 30); do
  S=$(fab api "workspaces/${WORKSPACE_ID}/git/status")
  WH=$(echo "$S" | jq -r '.text.workspaceHead')
  RC=$(echo "$S" | jq -r '.text.remoteCommitHash')
  CH=$(echo "$S" | jq -r '.text.changes | length')
  # Success = heads match AND no residual changes. Heads matching alone can still
  # leave uncommitted/unapplied diffs; validate both before declaring "synced".
  [ "$WH" = "$RC" ] && [ "$CH" = "0" ] && { echo "synced at $WH"; break; }
  sleep 8
done

For an operation still in flight (a 202 from commit/update), poll the LRO directly instead of guessing from heads:

# Capture x-ms-operation-id from the 202, then poll until Succeeded
OP_ID=$(fab api -X post "workspaces/${WORKSPACE_ID}/git/commitToGit" -i commit.json \
  --show_headers | jq -r '.headers["x-ms-operation-id"]')
while :; do
  ST=$(fab api "operations/${OP_ID}" | jq -r '.text.status')
  case "$ST" in Succeeded) break ;; Failed) echo "op failed"; break ;; *) sleep 5 ;; esac
done

git/status also returns a changes array — a non-empty array means there are uncommitted (workspace) or unapplied (remote) differences.

Resolve Conflicts

A genuine conflict is changed-on-both-sides: after the last sync, the same item was modified in the workspace (an uncommitted local edit) and on the branch (a new commit). git/status flags that item in its changes array with conflictType: "Conflict" (both workspaceChange and remoteChange show Modified). This is distinct from a one-sided diff, where only updateFromGit (remote-only change) or commitToGit (workspace-only change) is needed with no policy.

When both sides changed an item, updateFromGit requires a conflict policy — calling it without a conflictResolution block returns 400 MissingWorkspaceConflictResolution:

  • conflictResolutionPolicy: "PreferRemote" — the Git branch wins (typical for pull-into-target deployment); the local edit is discarded.
  • conflictResolutionPolicy: "PreferWorkspace" — the live workspace wins; to push the workspace version to Git instead use commitToGit, which does not take a conflict policy.

Set options.allowOverrideItems: true to let the update overwrite items that already exist in the target workspace (required whenever the conflicting item is already present in the workspace, which it is by definition in a both-sides conflict).

Which policy to pick:

ScenarioPolicy
Downstream/target workspace pulls the latest from GitPreferRemote
The authoring workspace is the source of truthPreferWorkspace (or commitToGit to push the workspace version to Git)
Genuine two-sided edits on the same itemInspect each changes entry's conflictType in git/status and resolve deliberately per item

Disconnect from Git

fab api -X post "workspaces/${WORKSPACE_ID}/git/disconnect"

Link a Branch Workspace to its Base (Workspace Relations)

Preview. The Git workspace relations APIs are part of the Fabric Git surface and are in preview. They record a lineage link between a branch workspace and the base workspace it was branched from, so the base↔branch relationship is visible in the Fabric portal. Use them when a branch workspace was created before the built-in branch-out feature shipped, or when branch workspaces are provisioned by your own automation (not the portal branch-out UX) and you still want that lineage to show in the UI. Creating a relation does not connect either workspace to Git, move items, or start a sync — it only records the link.

Precondition — both workspaces must point at the same Git root. Create fails with WorkspaceRelationRootDirectoryMismatch unless the base and branch workspaces are connected to the same repository root, differing only by branch:

  • Azure DevOps: same organizationName, projectName, repositoryName, and directoryName — only branchName differs.
  • GitHub: same repository URL and directoryName — only branchName differs.

So connect (and initialize) both workspaces to Git first; the relation just makes the existing branch/base pair visible.

Create a relation (link branch ↔ base)

The relation is bidirectional, so you create it once. Read the body carefully — the workspace you POST to and the relationType are two different things:

  • The URL workspace (workspaces/{workspaceId}/...) is the side you call from.
  • relatedWorkspaceId is the other workspace.
  • relationType describes the role of that other (relatedWorkspaceId) workspace, not the workspace in the URL. Only Base and Branch are valid when creating.

So the same single link has two equivalent ways to create it:

Call from (URL workspace)relatedWorkspaceIdrelationType
the branch workspacethe baseBase
the base workspacethe branchBranch

Do not run both — once one direction exists, creating the other fails with WorkspaceRelationBidirectionalExists. Whichever side you call from, the permission requirement is the same: Admin on the branch workspace and Contributor (or higher) on the base workspace. The recommended (and most intuitive) form is to call from the branch and point at the base:

# From the BRANCH workspace, declare that the related workspace is its Base.
printf '{"relatedWorkspaceId":"%s","relationType":"Base"}' "${BASE_WS}" > relation.json
fab api -X post "workspaces/${BRANCH_WS}/git/workspaceRelations" -i relation.json

Returns 201. Service principals and managed identities are supported.

List relations

Any Viewer or higher can list a workspace's relations (paginate via continuationToken). relationType in responses can be Base, Branch, or RelatedWorkspace.

fab api "workspaces/${WORKSPACE_ID}/git/workspaceRelations"

Delete a relation

Removes the lineage link only — it does not delete either workspace or its items. The caller must be Admin on the workspace named in the path (either the base or the branch side). Only a Branch relation is deletable — otherwise the API returns WorkspaceRelationTypeNotBranch.

fab api -X delete "workspaces/${WORKSPACE_ID}/git/workspaceRelations/${RELATION_ID}"

(Learn: rest/api/fabric/core/workspace-relations.)


Related: cross-workspace item binding

Updating a target workspace from the same branch promotes items into it. Whether each item-to-item reference rebinds automatically (logical IDs) or breaks (object IDs / hardcoded endpoints) is a property of the item definition format, not of the Git lifecycle, and applies equally to fabric-cicd and the Bulk Import API. To predict a specific dependency's verdict, read the evidence-tagged compatibility matrix, or empirically validate an uncertain reference, use the cross-workspace-item-binding skill (a planned companion skill, not yet available in this repo) — it is a distinct concern from the connect/commit/update lifecycle this skill drives.

Related: Terraform (infrastructure as code)

To manage the connect side declaratively (infrastructure as code) instead of with imperative fab api calls, use the git-integration-terraform skill (a planned companion skill, not yet available in this repo). It covers the microsoft/fabric provider's fabric_connection and fabric_workspace_git resources (Azure DevOps service principal or automatic, GitHub PAT, init strategy, plan/apply/import). In that pattern Terraform owns the connection, while the commit / update-from-git / status operations in this skill remain the imperative runtime path — so a common setup is Terraform for the connection plus a CLI step for update-from-git.

Related: Variable Library (environment parameterization)

Git integration moves item definitions across workspaces, but it does not by itself parameterize values that must differ per stage (dev/test/prod) — connection strings, endpoints, capacity IDs, feature flags. That per-environment parameterization is the job of a Variable Library: define the variables once, provide a value set per stage, and have items resolve the stage-appropriate value. When a Git-synced workspace needs values that vary by environment, use the variable-library-cli companion skill for the variable/value-set modeling instead of hardcoding or duplicating that logic here — this skill owns the source-control lifecycle, the Variable Library skill owns the parameterization it feeds.

Gotchas, Rules, Troubleshooting

SymptomCauseFix
400 WorkspaceHeadMismatchStale workspaceHeadRe-read git/status, retry with the current head
400 MissingWorkspaceConflictResolutionBoth sides changed the same item, but updateFromGit was sent without a conflictResolutionAdd conflictResolution (policy PreferRemote or PreferWorkspace) + options.allowOverrideItems: true; check git/status for conflictType: "Conflict" first
WorkspaceHasNoCapacityAssignedWorkspace not on a capacity (Git needs one)Assign a capacity to the workspace, then retry
404 GitProviderResourceNotFound on git/connectThe repo, branch, or directoryName does not exist on the provider — the API, unlike the portal, does not auto-create the folderVerify the repo/branch names; if the folder is new, pre-create it by committing a README.md placeholder to the branch via the Git provider API (see Connect a Workspace to Git), then retry
WorkspaceAlreadyConnectedToGitWorkspace is already linked to a repoDisconnect first, then connect to the new repo
InsufficientPrivileges / 403Caller lacks the role the operation needs (connect/disconnect need Admin; commit/update need at least Contributor with write on all items)Grant the caller the required role: Admin to connect/disconnect, or Contributor with write on all items to commit/update
PrincipalTypeNotSupportedService principal using automatic credentials (or GitHub)Switch to a ConfiguredConnection in myGitCredentials
commitToGitRequest is required (with fab api)Body piped via stdinWrite the body to a file, pass -i body.json
Operation returns 202 then nothing changesAsync not yet completePoll operations/{id} (from --show_headers), or poll git/status until heads match
Operation stuck in Running for a long timeLarge workspace or a second Git op is queuedKeep polling with a sane timeout (about 10 min); never start a second Git op concurrently
401 UnauthorizedWrong token audienceRun fab auth login (fab sets the audience automatically); on the az rest fallback pass --resource https://api.fabric.microsoft.com
WorkspaceRelationRootDirectoryMismatch on git/workspaceRelationsBase and branch workspaces are connected to different Git rootsConnect both to the SAME repo/org/project/directory (Azure DevOps) or repo URL/directory (GitHub), differing only by branchName, then retry
WorkspaceRelationAlreadyExists / WorkspaceRelationBidirectionalExistsThe base↔branch relation is already recorded (it is bidirectional)Nothing to do — GET git/workspaceRelations to confirm; do not also create the reverse direction
WorkspaceRelationTypeNotBranch on deleteTried to delete a relation that is not a Branch relationOnly Branch relations are deletable — use the correct workspaceRelationId and delete from the branch side
Every sync reports an uncommitted change you did not make (one removed blank line, or a CRLF/LF flip)In affected workspaces, an editor or AI agent authored the item source (notebook-content.py, pipeline-content.json, .platform) with a trailing final newline or CRLF, then Fabric re-serialized it to the observed export form (LF, no trailing newline)Confirm the behavior with a representative round trip, then match the observed export at authoring time: no trailing newline, LF endings. Pin it in the synced repo with .editorconfig + .gitattributes, and tell your AI agent not to add a final newline. See git-integration-concepts.md § Avoiding formatting-only diffs

Examples

Example 1: Commit source, then deploy into a target workspace

# Commit everything in the source workspace (jq builds a JSON-safe body)
SRC_HEAD=$(fab api "workspaces/${SRC_WS}/git/status" | jq -r '.text.workspaceHead')
jq -n --arg head "$SRC_HEAD" --arg msg "Deploy v1" \
  '{mode:"All", workspaceHead:$head, comment:$msg}' > commit.json
fab api -X post "workspaces/${SRC_WS}/git/commitToGit" -i commit.json
# ...poll src status until synced, then updateFromGit into ${TARGET_WS} (see above)

Example 2: az rest fallback equivalents

When fab is unavailable, the same REST endpoints work through az rest. Always pass --resource; a body can be inline (--body '{...}') or from a file (--body @file.json):

# status
az rest --method GET \
  --url "https://api.fabric.microsoft.com/v1/workspaces/${WORKSPACE_ID}/git/status" \
  --resource "https://api.fabric.microsoft.com"

# commit (body inline)
az rest --method POST \
  --url "https://api.fabric.microsoft.com/v1/workspaces/${WORKSPACE_ID}/git/commitToGit" \
  --resource "https://api.fabric.microsoft.com" \
  --headers "Content-Type=application/json" \
  --body "{\"mode\":\"All\",\"workspaceHead\":\"${HEAD}\",\"comment\":\"Author items\"}"