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-cliTelemetry — MANDATORY. Every
api.fabric.microsoft.comcall must carryx-ms-fabric-skill: git-integration-operations-cli(az rest:--headers "x-ms-fabric-skill=git-integration-operations-cli"), including every LRO poll,fabric_lroand retry. Snippets omit it — add it anyway.
CRITICAL NOTES
- To find a workspace ID from its name: list all workspaces, then filter by
displayNamewith JMESPath. To find an item ID: list items of that type in the workspace, then filter bydisplayName.- All Git operations are control plane calls against
https://api.fabric.microsoft.com. Runfab auth loginonce andfab apisets the base URL and Fabric token audience automatically. With theaz restfallback you must pass the matching--resourceyourself, or you get a 401.commitToGit,updateFromGit, and evengit/statuscan return HTTP 202 (long-running operation), not just a synchronous body. On202, poll the operation: withfab, readx-ms-operation-idfrom--show_headersand pollfab api operations/{id}untilstatusisSucceeded; with theaz restfallback, poll theLocation/x-ms-operation-idheader (see COMMON-CORE long-running operation polling). THEN readgit/statusto confirmworkspaceHead == remoteCommitHash. Prefer the operation poll to confirm completion; if you pollgit/statusinstead, treat an in-progress (202) or not-yet-synced response as "still running" and do not act on itschangesuntil the heads match. (Learn: rest/api/fabric/core/git and fabric/cicd/git-integration/git-automation.)updateFromGitrequires the workspace's currentworkspaceHead; a stale value returns400 WorkspaceHeadMismatch. Always readgit/statusfirst.- 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.- 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):
- COMMON-CLI.md § Finding Workspaces and Items in Fabric — Mandatory for resolving workspace/item IDs by name.
- COMMON-CORE.md § Authentication & Token Acquisition — wrong audience = 401; read before any auth issue.
- COMMON-CLI.md § Authentication Recipes —
az loginflows and token acquisition for CLI. - COMMON-CLI.md § Fabric Control-Plane API via az rest — the
az restfallback path (always pass--resource); includes LRO polling helpers. - COMMON-CORE.md § Core Control-Plane REST APIs — pagination, long-running-operation polling, rate limiting.
Table of Contents
Must/Prefer/Avoid
MUST DO
- Read
git/statusand pass the currentworkspaceHeadinto everycommitToGitandupdateFromGitcall. - Poll
git/statusafter each async operation untilworkspaceHead == remoteCommitHashand thechangesarray is empty. A202/Succeededstatus 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/updateFromGitwhile one is stillRunningcorrupts head tracking. Serialize: poll the first toSucceededbefore 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(afterfab auth login) as the primary driver: it sets the base URL and Fabric token audience automatically, avoiding the 401 wrong-audience trap. Useaz restwith--resource "https://api.fabric.microsoft.com"as the fallback whenfabis unavailable or you need fine-grained header control.PreferRemoteconflict resolution for a clean "pull latest from Git" into a downstream/target workspace.
AVOID
- Reusing a stale
workspaceHead(causes400 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>becomesaz rest --method GET --url "https://api.fabric.microsoft.com/v1/<endpoint>" --resource "https://api.fabric.microsoft.com".fab api -X post <endpoint> -i body.jsonadds--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.
| Check | Command | Pass = proceed / Fail = fix |
|---|---|---|
| Auth + Fabric token | fab auth login then fab api workspaces | 401 → re-login (fab sets the correct audience automatically) |
| Caller is workspace Admin | fab api "workspaces/${WORKSPACE_ID}/roleAssignments" | Connect/disconnect require the Admin role |
| Workspace has capacity | fab api "workspaces/${WORKSPACE_ID}" → check capacityId | WorkspaceHasNoCapacityAssigned → assign a capacity (Git requires one) |
| Git credential for the SP path | fab 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 branch | GitHub: curl -fsS -H "Authorization: Bearer $PAT" "https://api.github.com/repos/$OWNER/$REPO/contents/$DIR?ref=$BRANCH" — ADO: Items API on the repo | The 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
directoryNamemust already exist on the branch. Unlike the Fabric portal — which silently creates a missing folder by committing aREADME.mdplaceholder into it — thegit/connectAPI does not create the folder. Connecting to adirectoryNamethat does not exist on the branch fails with404 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 aREADME.mdwhose body isThis 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
myGitCredentialsfor 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:
For Azure DevOps, the connection's"myGitCredentials": { "source": "ConfiguredConnection", "connectionId": "<connection-guid>" }credentialTypecan be eitherServicePrincipal(headless automation) orOAuth2(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 thereposcope. 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.
initializeConnectionreturnsrequiredAction(UpdateFromGit,CommitToGit, orNone) plusworkspaceHeadandremoteCommitHash, and can itself return202. After it settles, call the sync operation named byrequiredActionwith 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
urlparameter: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 usecommitToGit, 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:
| Scenario | Policy |
|---|---|
| Downstream/target workspace pulls the latest from Git | PreferRemote |
| The authoring workspace is the source of truth | PreferWorkspace (or commitToGit to push the workspace version to Git) |
| Genuine two-sided edits on the same item | Inspect 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, anddirectoryName— onlybranchNamediffers. - GitHub: same repository URL and
directoryName— onlybranchNamediffers.
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. relatedWorkspaceIdis the other workspace.relationTypedescribes the role of that other (relatedWorkspaceId) workspace, not the workspace in the URL. OnlyBaseandBranchare valid when creating.
So the same single link has two equivalent ways to create it:
| Call from (URL workspace) | relatedWorkspaceId | relationType |
|---|---|---|
| the branch workspace | the base | Base |
| the base workspace | the branch | Branch |
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
| Symptom | Cause | Fix |
|---|---|---|
400 WorkspaceHeadMismatch | Stale workspaceHead | Re-read git/status, retry with the current head |
400 MissingWorkspaceConflictResolution | Both sides changed the same item, but updateFromGit was sent without a conflictResolution | Add conflictResolution (policy PreferRemote or PreferWorkspace) + options.allowOverrideItems: true; check git/status for conflictType: "Conflict" first |
WorkspaceHasNoCapacityAssigned | Workspace not on a capacity (Git needs one) | Assign a capacity to the workspace, then retry |
404 GitProviderResourceNotFound on git/connect | The repo, branch, or directoryName does not exist on the provider — the API, unlike the portal, does not auto-create the folder | Verify 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 |
WorkspaceAlreadyConnectedToGit | Workspace is already linked to a repo | Disconnect first, then connect to the new repo |
InsufficientPrivileges / 403 | Caller 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 |
PrincipalTypeNotSupported | Service principal using automatic credentials (or GitHub) | Switch to a ConfiguredConnection in myGitCredentials |
commitToGitRequest is required (with fab api) | Body piped via stdin | Write the body to a file, pass -i body.json |
Operation returns 202 then nothing changes | Async not yet complete | Poll operations/{id} (from --show_headers), or poll git/status until heads match |
Operation stuck in Running for a long time | Large workspace or a second Git op is queued | Keep polling with a sane timeout (about 10 min); never start a second Git op concurrently |
401 Unauthorized | Wrong token audience | Run fab auth login (fab sets the audience automatically); on the az rest fallback pass --resource https://api.fabric.microsoft.com |
WorkspaceRelationRootDirectoryMismatch on git/workspaceRelations | Base and branch workspaces are connected to different Git roots | Connect both to the SAME repo/org/project/directory (Azure DevOps) or repo URL/directory (GitHub), differing only by branchName, then retry |
WorkspaceRelationAlreadyExists / WorkspaceRelationBidirectionalExists | The 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 delete | Tried to delete a relation that is not a Branch relation | Only 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\"}"