Make exported and decompiled Bicep compile and redeploy: a deterministic de-cycler that
removes the dependency cycles bicep decompile introduces, plus an AI "skills" cleanup pass that
fixes the semantic issues that only surface when you actually re-deploy.
Independent, community proof-of-concept. Not a Microsoft product and not affiliated with or endorsed by Microsoft. Client-side, zero dependencies, no Azure credentials required to run the fix. The AI cleanup makes semantic edits: always review the diff and validate in a non-production subscription before deploying. Provided as is, with no warranty and no liability; use at your own risk (see DISCLAIMER.md). Validated end to end against live Azure (see TESTING.md and docs/what-we-tested-and-benefits.md).
bicep-decycle/- the de-cycler script (deterministic, no AI). Breaks the BCP080 reference cycles safely and reports exactly what it could not resolve. Same input, same output, every time; no model, no network. This is enough on its own for simple resource groups.ai-polish/- the AI "skills" cleanup pass (judgement calls). Fixes what needs Azure resource knowledge, not just graph rewriting: self-references, read-only back-references, dropped secret values, and platform-managed noise. Shipped both as a written skills file (ai-polish/SKILL.md) you can hand to an agent, and as a runnable reference implementation (ai-polish/3-ai-polish.js).
Run the script first, then the skills pass. See docs/what-we-tested-and-benefits.md for exactly which improvement comes from which half.
The standard way to get Bicep from existing resources is az group export (or the Portal
"Export template" button) followed by bicep decompile. The decompiler rewrites ARM
resourceId('...') strings, which carry no dependency, into symbolic references
(other.id), which do. Any resources that reference each other (hub/spoke VNet peering,
parent and child, cross-resource ids in properties) then come out of decompile as a
dependency cycle, and bicep build fails:
Error BCP080: The expression is involved in a cycle ("vnet_hub" -> "vnet_spoke").
The cycle was never in the ARM template. It is an artifact of decompilation. This repo removes it, and proves the result still deploys.
bicep-decycle/ the fix: standalone tool (engine + CLI + tests + fixtures)
decycle.js parse -> reference graph -> Tarjan SCC -> minimal .id cuts
cli.js node cli.js broken.bicep --out fixed.bicep --check
test.js 8 offline checks (all pass)
fixtures/ repros (vnet peering inline + inline&child, storage, 3-node, parent) + a clean control
README.md tool reference
ai-polish/ the AI "skills" cleanup pass (runs after the de-cycler)
SKILL.md the cleanup rules as a tool-agnostic skills file
3-ai-polish.js runnable, rule-based reference implementation of those rules
README.md what it fixes and why it is separate from the script
demo/ offline, no-Azure demo of the three lanes (raw -> +script -> +AI)
run-demo.ps1 / run-demo.sh raw FAILS -> + de-cycler FAILS -> + AI PASSES
1-raw-export.bicep synthetic, sanitized "as exported" input
docs/
what-we-tested-and-benefits.md what was tested + which half gives you which improvement
examples/azure-roundtrip/ the real Azure test evidence for the hub/spoke case
deploy.json baseline template (hub/spoke + storage)
exported-arm.json az group export output
exported.bicep decompiled (BEFORE: fails bicep build)
exported.fixed.bicep de-cycled (AFTER: passes bicep build)
exported.redeploy.bicep one changed parameter (redeployed successfully)
decycle-report.json machine-readable log of every edge rewritten
logs/ raw before-build / decycle / redeploy console output
examples/per-scenario/ per-scenario round-trip summary (results.json + table)
SOLUTION.md design write-up and root-cause analysis
TESTING.md the live Azure round-trip, with real commands and outputs
For a minimal set of .id edges lying on a cycle, rewrite
id: vnet_spoke.idinto
id: resourceId('Microsoft.Network/virtualNetworks', 'vnet-spoke')resourceId(...) evaluates to the identical id at deploy time but is not a symbolic
reference, so it carries no compile-time edge. The acyclic intent the ARM already had is
restored. parent: and dependsOn edges are never touched.
# fix a decompiled file and prove it compiles
node bicep-decycle/cli.js broken.bicep --out fixed.bicep --check
# from a live resource group
az group export -g <rg> --skip-all-params > export.json
bicep decompile export.json --outfile main.bicep # may fail with BCP080
node bicep-decycle/cli.js main.bicep --out after-script.bicep --check
# then apply the AI skills cleanup pass (self-refs, read-only props, secrets, noise)
node ai-polish/3-ai-polish.js after-script.bicep final.bicep
bicep build final.bicepExit code is non-zero if any cycle cannot be broken or if --check finds the output does
not compile, so it is safe to wire into CI. Want to see it without an Azure subscription? Run the
offline demo in demo/.
- Offline:
node bicep-decycle/test.js-> 8/8 pass; every fixture failsbicep buildbefore and passes after; a clean file is returned byte for byte unchanged. Thedemo/reproduces all three lanes (raw FAIL -> + script FAIL -> + AI PASS) with no cloud. - Live Azure end to end: deploy -> export -> decompile (fails BCP080) -> de-cycle (passes) -> change a parameter -> redeploy via CLI (Succeeded) -> verify. Full transcript and artifacts in TESTING.md.
- Per scenario and at scale: eight isolated one-or-two-resource round-trips all re-deploy after script + AI, and a single resource group spanning 50-plus top resource types went from an 806-resource export that does not build to 76 resources that re-deploy 76/76. Details, and which half gives you which improvement, in docs/what-we-tested-and-benefits.md.
The goal of sharing this is to find out whether a client-side de-cycler plus an AI cleanup pass is
worth investing in. If bicep decompile handed you a BCP080 cycle, please open a
Share a failing export issue with the
error and a sanitized snippet. See CONTRIBUTING.md.
Independent community proof-of-concept, shared to gather early-adopter feedback. This is a client-side mitigation while the decompiler behavior is addressed upstream in the Bicep client. It is not an official Microsoft product or support channel, is not affiliated with or endorsed by Microsoft, and comes with no warranty (see DISCLAIMER.md). Licensed under the MIT License (see LICENSE).
Azure, Bicep, and Microsoft are trademarks of the Microsoft group of companies. They are used here only to describe what this project interoperates with, and their use does not imply any affiliation or endorsement.