Inspecting BC binaries

SkillDev tools

Reference for reading BC's own assemblies, setting up and loading the bc-decompiler MCP server, reading list_contexts correctly, citing a BC binary by build and hash rather than by version label, and why `strings`/`strings -el` cannot answer "is this member referenced". Load it before you decompile, compare two BC versions, or byte-scan a .NET assembly. The directives live in CLAUDE.md § "Code navigation" (2c, 3b).

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Inspecting BC binaries skill

What this skill tells your AI

The instructions your AI receives, as published by stefanmaron/businesscentral.al.runner in .claude/skills/inspecting-bc-binaries/SKILL.md and read by ahel’s review.

CLAUDE.md § 2c and § 3b state the rules; this is the reference behind them.

Setting up the bc-decompiler MCP server

tools/setup-bc-decompiler.sh      # needs the .NET 10 SDK; the runner itself stays on net8.0

That clones and publishes pardeike/DecompilerServer into $DECOMPILER_SERVER_DIR (default ~/Documents/Repos/tools/DecompilerServer) and prints the block to put in .mcp.json at the repository root:

{ "mcpServers": { "bc-decompiler": { "type": "stdio", "command": "dotnet",
    "args": ["<DEST>/publish/DecompilerServer.dll"], "env": {} } } }

Two things about that file. .mcp.json is gitignored — per-machine, never committed. And it is read only at session start, so a freshly written one does nothing for the session you are in: "configured" and "usable right now" are different states, and only a restart turns the first into the second. tools/preflight.py tells them apart by speaking MCP to the server itself; in-session, mcp__bc-decompiler__status is the check.

A context needs its BC artifacts on disk first — load_assembly points at <artifacts>/<ver>/Microsoft.Dynamics.Nav.Ncl.dll under ~/.local/share/al-runner/artifacts/ (or $AL_RUNNER_ARTIFACTS_ROOT), so a version that was never provisioned cannot be decompiled. al-runner provision --bc-version <ver> fetches it; then load once per version (contexts persist in ~/.decompilerserver/ across restarts):

load_assembly(assemblyPath: "<artifacts>/<ver>/Microsoft.Dynamics.Nav.Ncl.dll",
              additionalSearchDirs: ["<artifacts>/<ver>"], contextAlias: "bc284")

list_contexts: registered is not loaded

registeredAliases lists every alias that can be activated; items lists the contexts actually loaded. An alias in the first and not the second means nothing has been read through it, so a cross-version comparison resting on it is an inference, not a measurement. Measured: an agent read bc270/bc273 sharing an MVID from items, saw bc275 in registeredAliases, and concluded 27.5 was a distinct binary; hashing the files showed all three Ncl.dll byte-identical. Hash the artifact, or load the context, before claiming two versions differ.

Cite the build and the hash, never the version label

The mirror error is the commoner one: citing ONE binary under a version label that implies independence. The builds 27.0.38460.53934, 27.3.44313.53909 and 27.5.46862.53931 are one file (sha256 affa03c9…, 10716984 bytes), so "measured on 27.5" and "27.0, 27.3 and 27.5 agree" can be the same single measurement wearing three labels — and nothing in the first phrasing looks like a claim about independence, which is why it passes review.

But the version label does not identify the binary either way, so neither does a 27.x/28.x boundary. A two-part version covers several builds and they are not all the same file: 27.5.46862.48827 (0a6ce45e…) differs from 27.5.46862.53931 (affa03c9…), and the 28.4.53241 builds are not one binary either. So "one anywhere inside 27.x is one measurement" is false in both directions — two 27.5 results can be two binaries, and 28.0 through 28.4 can be one. Cite the build, and the hash: 27.5.46862.53931 (affa03c9) is a measurement, 27.5 is not. sha256sum over the artifact directories is the whole check, and tools/test_bc_binary_identity_claims.py pins the groupings this section states against whatever is provisioned. Measured three times: #3372 (the over-claim above), #3859, where an agent deleted a stale waiver recording exactly this hazard and then made the error the waiver had described, and #4221, where this section's own example was the wrong shape.

strings cannot tell you whether a member is referenced

"C# string literals are UTF-16, so strings | grep false-negatives on .NET assemblies; use strings -a -el" circulates in agent briefs here and is half right — and the wrong half is the half people reach for it with. A .NET assembly keeps the two in different heaps:

whatheapencodingthe flag that finds it
member / type / namespace names#StringsUTF-8strings -a
user string literals ("out-of-scope: ...")#USUTF-16strings -a -el

Measured on 28.1.49838.53910/Microsoft.Dynamics.Nav.Ncl.dll, for the metadata name RunRequestPageAsync: strings -a -el finds 0, strings -a finds 4. So an agent told to use -el and asked whether a member is referenced gets a clean zero and reads it as a finding (tools/test_strings_heap_claim.py pins that sentence).

A non-zero from -el does not rescue the method. Some identifier-like #Strings names also occur verbatim in #US — ALDownloadFromStream answers 1 under -el — because some literal happens to spell the same text. That hit is never the metadata entry, and a scan that is usually right by accident is worse than one that is always wrong, because the failures look like data.

For "is this member referenced", read the MemberReference table, not bytes. A MemberReference means this assembly calls the member, a MethodDefinition that it defines it. On PR #4224 that split was load-bearing — the .app chunk referencing NavReport::RunRequestPageAsync is also the one defining RunReportRequestPage, and only the table distinguishes them (#4229). Use the mcp__bc-decompiler__* tools (find_callers, search_members) or read the tables with System.Reflection.Metadata.

Signals

GitHub stars
37
Forks
14
Last commit
Sep 2026
Advanced
Item type
skill
Key
inspecting-bc-binaries
Source
github.com/stefanmaron/businesscentral.al.runner