Skip to content

setup blueprint: agentBlueprintClientSecret persists as null on macOS despite successful credential creation #408

Description

@SadiqJaf

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

  1. 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).
  2. 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.
  3. Sealing: investigate whether the same flow that loses the secret also leaves completed: false.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions