Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Fix the deploy-time foot-guns of Databricks Asset Bundles (DAB) and the infrastructure operations around them: the bundle-bind gap for UC catalogs and external locations, the "unexpected EOF reading terraform.tfstate" redeploy failure, the schema-GRANT-ordering bug that fails the first deploy, customer-managed-key (CMK) rotation that requires draining the whole workspace, and the PrivateLink cost leak where S3/STS/Kinesis still traverse the NAT. Includes a PreToolUse hook that backs up and valid
.claude/skills/jeremylongshore-databricks-bundle-medic/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 133% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 80% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 140% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 81% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 192% | 0% |
The deploy + infrastructure spine of the pack. Databricks Asset Bundles (DAB) is immature tooling — the replacement for the deprecated dbx, with a moving bug list across CLI versions — and the infrastructure operations around a deploy (encryption keys, networking, workspace config) bite production platform teams hardest. This skill turns the five worst deploy-time foot-guns into deterministic, reversible operations.
Five pains, all from the pack's deploy-ops research:
Asset Bundles (DAB). D4 — databricks bundle bind does NOT yet support UC catalogs or external locations (databricks/cli#4842), so existing UC resources cannot come under bundle management without hand-editing terraform.tfstate (hostile) or destroy-and-recreate (impossible with dependent tables). D5 — "unexpected EOF reading terraform.tfstate" on every redeploy after the first (databricks/cli#4986), which bricks the bundle until you switch to DATABRICKS_BUNDLE_ENGINE=direct. D6 — the schema GRANT-ordering bug (databricks/cli#4573): the first deploy to a fresh workspace fails with "User does not have CREATE TABLE on Schema", but the second succeeds because the first applied the grants before dying.
Deploy-time infrastructure. D8 — rotating a workspace's customer-managed key (CMK) requires terminating every cluster, instance pool, and SQL warehouse first: a hard maintenance window. D9 — the PrivateLink trap: enabling PrivateLink for the control plane is mistaken for "all traffic is private," but the data plane's S3 / STS / Kinesis calls still traverse the NAT, billing NAT-processing + cross-AZ transfer until each gets its own VPC endpoint.
The two hooks (this is the pack's only two-hook skill). A PreToolUse hook (hooks/bundle-deploy-guard.py) runs before every databricks bundle deploy: it validates the bundle's local terraform state parses as JSON, caches a timestamped known-good backup as a recovery escape hatch, and warns loudly if the state is corrupt or has shrunk (the D5 signature). A PostToolUse hook (hooks/bundle-grant-retry.py) watches a deploy's output and — ONLY on the exact D6 "does not have … on Schema" signature — adds context recommending one retry, with the reason. Both are advisory: the pre-hook never blocks a deploy, and the post-hook never masks a real error — it surfaces the diagnosis and stops recommending retries if the same failure persists.
Deterministic work lives in scripts/; deep knowledge in references/. The import-uc-resource-to-bundle.py D4 workaround is self-deprecating — it exists only until #4842 closes and says so. Control-plane state comes from the databricks-workspace-mcp server (external_locations_list, storage_credentials_list) or the CLI; advisory-mode fallback accepts pasted input.
terraform and jq on PATH — for the bundle,import, and state operations.
databricks-workspace-mcp registered (optional) — for external_locations_listand storage_credentials_list. Absent → the CLI (databricks external-locations list) or advisory mode on pasted input.
are account-scoped; a workspace PAT is insufficient. AWS/Azure/GCP CLI for the cloud-side key + VPC-endpoint work.
unless a state looks corrupt; the post-hook is silent unless the exact D6 error fires.
Pick the flow by symptom. Always name the exact error string / issue id — unexpected EOF reading terraform.tfstate (#4986), does not have CREATE TABLE on Schema (#4573), the bundle bind UC gap (#4842) — an operator greps for those.
"unexpected EOF reading terraform.tfstate" after the first deploy is #4986. The bundle-deploy-guard PreToolUse hook already cached a known-good backup; restore it, or switch engines:
bashDATABRICKS_BUNDLE_ENGINE=direct databricks bundle deploy -t "$TARGET"
Engine tradeoffs + the migration steps: ${CLAUDE_SKILL_DIR}/references/bundle-engine-tradeoffs.md.
This is the transient GRANT-ordering bug (#4573). The bundle-grant-retry PostToolUse hook flags it; re-run the SAME deploy exactly once — the failed first pass already applied the grants. If the identical error persists after one retry, it is NOT this transient — treat it as a real missing privilege and stop retrying.
databricks bundle bind cannot take a UC catalog or external location yet (#4842). Generate a review-first Terraform import plan (never hand-edit state):
bashpython3 "${CLAUDE_SKILL_DIR}/scripts/import-uc-resource-to-bundle.py" \ --type external_location --name raw_zone --resource-key raw_zone_loc
The three workarounds, ranked by risk: ${CLAUDE_SKILL_DIR}/references/uc-resource-binding-workarounds.md.
CMK rotation needs the whole workspace drained. Inventory the running compute (via external_locations_list / the CLI), then plan the drain — dry-run by default:
bashdatabricks clusters list --output json > /tmp/inv-clusters.json # + warehouses, pools python3 "${CLAUDE_SKILL_DIR}/scripts/drain-workspace.py" --inventory inv.json # dry-run python3 "${CLAUDE_SKILL_DIR}/scripts/drain-workspace.py" --inventory inv.json --execute --manifest drain.json # ... rotate the CMK per cloud, then: python3 "${CLAUDE_SKILL_DIR}/scripts/drain-workspace.py" --resume drain.json
Per-cloud playbooks (AWS/Azure/GCP): ${CLAUDE_SKILL_DIR}/references/cmk-rotation-by-cloud.md.
PrivateLink covers the control plane only. Audit the data-plane VPC for the S3/STS/Kinesis endpoints and emit remediation Terraform for any that are missing:
bashpython3 "${CLAUDE_SKILL_DIR}/scripts/audit-vpc-endpoints.py" \ --present s3 --vpc-id vpc-abc --region us-east-1 --emit-terraform
The full per-service leak/fix map: ${CLAUDE_SKILL_DIR}/references/cost-leak-map.md.
backup and, if the state is corrupt/shrunk, the #4986 recovery message.
with the reason (never a masked error).
resources: YAML + Terraform import block/command toadopt an existing UC resource, with the self-deprecation notice.
rotation and the manifest to resume exactly what was drained.
S3/STS/Kinesis endpoints, with remediation Terraform.
| Error | Cause | Solution | |-------|-------|----------| | unexpected EOF reading terraform.tfstate | Terraform-engine state read bug on redeploy (D5, #4986) | Restore the guard's cached backup, or DATABRICKS_BUNDLE_ENGINE=direct. | | User does not have CREATE TABLE on Schema '…' | DAB GRANT-ordering (D6, #4573) | Re-run the deploy once; grants were applied by the failed pass. Persists after one retry → real missing grant. | | bundle bind rejects a UC catalog / external location | Unsupported in the CLI (D4, #4842) | Use import-uc-resource-to-bundle.py to generate a Terraform import plan; never hand-edit state. | | CMK rotation rejected — compute still running | CMK update requires a drained workspace (D8) | drain-workspace.py --execute, rotate, then --resume. | | High NAT / cross-AZ cost after PrivateLink | S3/STS/Kinesis have no VPC endpoint (D9) | audit-vpc-endpoints.py --emit-terraform; add Gateway (S3) + Interface (STS/Kinesis) endpoints. |
bundle deploy fails with unexpected EOF reading terraform.tfstate."D5 (#4986). The bundle-deploy-guard hook already cached a known-good state before the deploy — restore it, then redeploy with DATABRICKS_BUNDLE_ENGINE=direct (the direct engine never reads the state file, so the EOF class cannot fire).
D6 (#4573). The bundle-grant-retry hook recognises the exact signature and recommends one retry — the failed first deploy applied the schema grants, so the second succeeds.
D4 (#4842) — bundle bind can't. import-uc-resource-to-bundle.py --type external_location emits the resources: YAML plus a Terraform import block to adopt it without recreating it or hand-editing state. The script self-deprecates when #4842 closes.
D9. audit-vpc-endpoints.py --present s3 finds STS and Kinesis have no Interface endpoint, so credential-vending and log traffic still cross the NAT — it emits the remediation Terraform for both.
${CLAUDE_SKILL_DIR}/references/uc-resource-binding-workarounds.md — the D4 bundle bind gap + 3 ranked workarounds (#4842).${CLAUDE_SKILL_DIR}/references/bundle-engine-tradeoffs.md — Terraform vs direct engine, the D5 EOF bug (#4986).${CLAUDE_SKILL_DIR}/references/cmk-rotation-by-cloud.md — AWS/Azure/GCP CMK rotation playbooks + the drain requirement (D8).${CLAUDE_SKILL_DIR}/references/cost-leak-map.md — the PrivateLink S3/STS/Kinesis NAT cost leak + fix map (D9).${CLAUDE_SKILL_DIR}/scripts/import-uc-resource-to-bundle.py — self-deprecating UC-resource import-plan generator (D4).${CLAUDE_SKILL_DIR}/scripts/drain-workspace.py — idempotent drain + resume for CMK rotation (D8).${CLAUDE_SKILL_DIR}/scripts/audit-vpc-endpoints.py — VPC-endpoint audit + remediation Terraform (D9).${CLAUDE_SKILL_DIR}/hooks/bundle-deploy-guard.py — PreToolUse state backup + validation (D5).${CLAUDE_SKILL_DIR}/hooks/bundle-grant-retry.py — PostToolUse D6 retry recommendation (D6).| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 13,051 | 14,586 | +12% | 1 | 1 | 0% | 2,198 | 5,113 | +133% | 0 | 0 | — |
case-02 | pass→fail | 27,453 | 9,777 | -64% | 1 | 1 | 0% | 3,831 | 4,783 | +25% | 0 | 0 | — |
case-03 | pass→pass | 20,284 | 66,364 | +227% | 1 | 1 | 0% | 3,681 | 7,138 | +94% | 0 | 0 | — |
case-04 | fail→pass | 15,504 | 7,063 | -54% | 1 | 1 | 0% | 2,345 | 4,217 | +80% | 0 | 0 | — |
case-05 | fail→pass | 11,102 | 6,043 | -46% | 1 | 1 | 0% | 1,700 | 4,072 | +140% | 0 | 0 | — |
case-06 | pass→fail | 9,000 | 8,023 | -11% | 1 | 1 | 0% | 1,543 | 4,428 | +187% | 0 | 0 | — |
case-07 | pass→pass | 12,201 | 4,869 | -60% | 1 | 1 | 0% | 1,641 | 3,819 | +133% | 0 | 0 | — |
case-08 | fail→pass | 15,054 | 6,956 | -54% | 1 | 1 | 0% | 2,317 | 4,198 | +81% | 0 | 0 | — |
case-09 | fail→pass | 9,838 | 7,990 | -19% | 1 | 1 | 0% | 1,527 | 4,458 | +192% | 0 | 0 | — |
case-10 | fail→pass | 14,816 | 8,936 | -40% | 1 | 1 | 0% | 2,243 | 4,497 | +100% | 0 | 0 | — |
case-11 | pass→pass | 14,170 | 11,282 | -20% | 1 | 1 | 0% | 2,121 | 4,884 | +130% | 0 | 0 | — |
case-12 | fail→pass | 8,596 | 6,406 | -25% | 1 | 1 | 0% | 1,419 | 4,342 | +206% | 0 | 0 | — |
case-13 | pass→pass | 6,922 | 4,349 | -37% | 1 | 1 | 0% | 1,018 | 3,674 | +261% | 0 | 0 | — |
case-14 | pass→pass | 16,887 | 8,826 | -48% | 1 | 1 | 0% | 2,877 | 4,785 | +66% | 0 | 0 | — |
case-15 | fail→pass | 8,607 | 7,961 | -8% | 1 | 1 | 0% | 1,156 | 4,191 | +263% | 0 | 0 | — |
case-16 | pass→pass | 6,233 | 3,891 | -38% | 1 | 1 | 0% | 930 | 3,715 | +299% | 0 | 0 | — |
case-17 | pass→pass | 9,020 | 7,787 | -14% | 1 | 1 | 0% | 1,314 | 4,400 | +235% | 0 | 0 | — |
case-18 | pass→pass | 15,626 | 6,460 | -59% | 1 | 1 | 0% | 2,231 | 4,156 | +86% | 0 | 0 | — |
case-19 | pass→pass | 8,859 | 6,605 | -25% | 1 | 1 | 0% | 1,529 | 4,079 | +167% | 0 | 0 | — |
case-20 | pass→pass | 8,915 | 7,219 | -19% | 1 | 1 | 0% | 1,434 | 4,238 | +196% | 0 | 0 | — |
case-21 | pass→pass | 12,049 | 11,689 | -3% | 1 | 1 | 0% | 2,069 | 5,093 | +146% | 0 | 0 | — |
case-22 | fail→fail | 9,531 | 9,241 | -3% | 1 | 1 | 0% | 1,626 | 4,485 | +176% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.