setup blueprint: agentBlueprintClientSecret persists as null on macOS despite successful credential creation
Summary
a365 setup blueprint --agent-name <name> [--m365] mints a client secret on the blueprint Entra app successfully (verified server-side via az ad app credential list), but the local a365.generated.config.json ends up with agentBlueprintClientSecret: null. The CLI logs the secret-write step as a success, so callers depending on the local config see "success → null secret" with no error signal.
This forces every subsequent CLI command that needs the secret (any local-runtime / update-endpoint / agent bridge work) to fail until the operator manually mints a replacement credential and patches the JSON.
Repro
$ a365 --version
1.1.174+25970fb6e6
$ rm -f a365.generated.config.json # start clean
$ a365 setup blueprint --agent-name "Hermes Inbox Helper R6" --m365
…
Creating blueprint client secret...
DPAPI encryption not available on this platform. Secret will be stored in plaintext.
Client secret created successfully!
- Secret stored in generated config (encrypted: False)
IMPORTANT: The client secret has been stored in a365.generated.config.json
…
(setup blueprint completes with exit code 0)
$ python3 -c "import json; print(json.load(open('a365.generated.config.json'))['agentBlueprintClientSecret'])"
None
$ az ad app credential list --id <agentBlueprintId> -o table
KeyId DisplayName StartDate EndDate
<id> hermes-bridge-... 2y 2026-05-07T15:35:33Z 2028-05-07T15:35:33Z
So the credential exists on the Entra app — only the local persistence is broken.
Expected
The CLI either:
- writes the freshly-minted password to
agentBlueprintClientSecret in a365.generated.config.json (the success log says it did), or
- emits a non-zero exit and a clear error if it cannot write (DPAPI-fallback failure, permissions, etc.).
Observed
- Exit code:
0
- Log lines (verbatim, in order):
Creating blueprint client secret...
DPAPI encryption not available on this platform. Secret will be stored in plaintext.
Client secret created successfully!
- Secret stored in generated config (encrypted: False)
IMPORTANT: The client secret has been stored in a365.generated.config.json
- On disk:
{
"agentBlueprintId": "<set>",
"agentBlueprintClientSecret": null,
"agentBlueprintClientSecretProtected": false,
"cliVersion": "1.1.174.9623",
"completed": false,
…
}
completed: false is also notable: setup blueprint exits 0 but doesn't seal the file as completed. (May or may not be related — flagging in case it points at the same code path.)
Reliability
Reproduces 100% across four consecutive walkthroughs:
| Round |
Date |
CLI version |
Result |
| 3 |
2026-05-05 |
1.1.171.4547 |
secret null on disk; manual recovery |
| 4 |
2026-05-05 |
1.1.171.4547 |
same |
| 5 |
2026-05-06 |
1.1.171.4547 |
same |
| 6 |
2026-05-07 |
1.1.174.9623 |
same — 1.1.174 did not fix this |
Environment
- Host: macOS 26.4.1 (Apple Silicon)
- .NET runtime:
dotnet --version 9.x (Homebrew install, DOTNET_ROOT=/opt/homebrew/opt/dotnet/libexec)
- a365 CLI version (NuGet identifier):
1.1.174+25970fb6e6
- a365 CLI version (runtime,
cliVersion in generated config): 1.1.174.9623
- Tenant: M365 with Frontier Preview Program + Microsoft Agent 365 Tier 3 license
- Az CLI:
2.x signed in to the same tenant, Global Administrator role
Likely cause (from the log line)
DPAPI encryption not available on this platform. Secret will be stored in plaintext. — the CLI correctly identifies that DPAPI is Windows-only and chooses the plaintext-write fallback path. The bug appears to be in that fallback path: it probably writes the generated config before the secret value is bound, or writes the in-memory dataclass without the freshly-minted password, then never overwrites with the populated value.
The contradiction Client secret created successfully! / Secret stored in generated config / agentBlueprintClientSecret: null strongly suggests the success-log fires unconditionally regardless of whether the persistence succeeded.
Recovery (the manual workaround we run on every walkthrough)
az ad app credential reset --id <agentBlueprintId> --append \
--display-name "hermes-bridge-rN" --years 2 -o json
# patch the resulting `.password` into a365.generated.config.json
# at the agentBlueprintClientSecret key
chmod 600 a365.generated.config.json
We've automated this in our wrapper at satscryption/Hermes-A365#14 as a stop-gap. The wrapper detects the agentBlueprintId set + agentBlueprintClientSecret null post-condition after setup blueprint and either prints a paste-ready recovery hint or, with an opt-in flag, runs the az recovery + patches the generated config + tightens to mode 0600.
Suggested fixes
- Persistence: in the non-DPAPI path, ensure the freshly-minted password is bound to the in-memory generated-config object before the file is written (or write again after the credential mint completes).
- Logging: don't emit "Client secret created successfully!" / "Secret stored in generated config" / "IMPORTANT: The client secret has been stored…" unless the post-write read-back confirms the value is present. If the write didn't take, exit non-zero with a clear message.
- Sealing: investigate whether the same flow that loses the secret also leaves
completed: false.
Related
setup blueprint:agentBlueprintClientSecretpersists asnullon macOS despite successful credential creationSummary
a365 setup blueprint --agent-name <name> [--m365]mints a client secret on the blueprint Entra app successfully (verified server-side viaaz ad app credential list), but the locala365.generated.config.jsonends up withagentBlueprintClientSecret: null. The CLI logs the secret-write step as a success, so callers depending on the local config see "success → null secret" with no error signal.This forces every subsequent CLI command that needs the secret (any local-runtime /
update-endpoint/ agent bridge work) to fail until the operator manually mints a replacement credential and patches the JSON.Repro
So the credential exists on the Entra app — only the local persistence is broken.
Expected
The CLI either:
agentBlueprintClientSecretina365.generated.config.json(the success log says it did), orObserved
0{ "agentBlueprintId": "<set>", "agentBlueprintClientSecret": null, "agentBlueprintClientSecretProtected": false, "cliVersion": "1.1.174.9623", "completed": false, … }completed: falseis also notable:setup blueprintexits 0 but doesn't seal the file as completed. (May or may not be related — flagging in case it points at the same code path.)Reliability
Reproduces 100% across four consecutive walkthroughs:
Environment
dotnet --version9.x (Homebrew install,DOTNET_ROOT=/opt/homebrew/opt/dotnet/libexec)1.1.174+25970fb6e6cliVersionin generated config):1.1.174.96232.xsigned in to the same tenant, Global Administrator roleLikely cause (from the log line)
DPAPI encryption not available on this platform. Secret will be stored in plaintext.— the CLI correctly identifies that DPAPI is Windows-only and chooses the plaintext-write fallback path. The bug appears to be in that fallback path: it probably writes the generated config before the secret value is bound, or writes the in-memory dataclass without the freshly-minted password, then never overwrites with the populated value.The contradiction
Client secret created successfully! / Secret stored in generated config / agentBlueprintClientSecret: nullstrongly suggests the success-log fires unconditionally regardless of whether the persistence succeeded.Recovery (the manual workaround we run on every walkthrough)
We've automated this in our wrapper at satscryption/Hermes-A365#14 as a stop-gap. The wrapper detects the
agentBlueprintId set + agentBlueprintClientSecret nullpost-condition aftersetup blueprintand either prints a paste-ready recovery hint or, with an opt-in flag, runs theazrecovery + patches the generated config + tightens to mode0600.Suggested fixes
completed: false.Related
--m365runs,botMsaAppId,botId, andmessagingEndpointare alsonullaftersetup blueprint --m365 --applydespite the wrapper printing a "messaging endpoint configured" line. May be the same root cause; happy to file separately if useful.setup permissions botcosmetic-logging defect; the queued fixes for that have shipped in 1.1.174.