muse mist create --sign silently fails to sign outside a hub-configured directory
muse mist create --sign silently fails to sign outside a hub-configured directory
Status
✅ All three phases complete (2026-07-06). Implemented TDD-first on branch
task/mist-sign-hub-context:
- Phase 1 (red):
MISTSIGN_01/02/03added totests/test_mist_cli.py, confirmed failing against pre-fix code —MISTSIGN_01gotsigned: False,MISTSIGN_02gotget_signing_identitycalled with noremote_url,MISTSIGN_03got silence on stderr instead of a warning. - Phase 2 (fix):
run_create's signing block now resolveshub_urlthe same way--pushdoes (hub_override, else repo-local config via_get_hub_url()) and passes it asget_signing_identity(remote_url=hub_url). Prints a clear stderr warning when no hub context resolves at all, instead of silent failure. One correction found mid-implementation: a freshauth keygenalone saveshandle: ""— onlyauth registersets a real handle, whichresolve_signing_identityrequires to be non-empty.MISTSIGN_01had to additionally mockregister's network calls (mirroringtest_cmd_auth_phase5.py's_run_keygen_and_registerpattern) to produce a fully valid identity, not keygen alone. - Phase 3 (regression + audit): all 95 tests in
test_mist_cli.pypass (including pre-existingM1/M2/M3).grep -rn "get_signing_identity()" muse/now returns zero real call sites (only a comment referencing the fixed bug).muse code test --jsonrun for downstream impact.
Committed as sha256:f7505032a4d5531c801c071fbd3f51880c7cb3d007077e724bbfd4c44277f586
on branch task/mist-sign-hub-context, not yet merged to dev.
Background
Discovered live while creating a mist to share on social channels: muse mist create ... --sign --push --hub https://staging.musehub.ai returned
"signed": false with no error, no warning — despite muse auth whoami --hub https://staging.musehub.ai confirming a fully configured identity
(handle: gabriel, key_set: true).
Root cause, traced and confirmed live (not guessed):
muse/cli/commands/mist.py:377 calls get_signing_identity() with zero
arguments inside run_create's signing block:
if do_sign:
try:
from muse.cli.config import get_signing_identity
from muse.core.keypair import sign_bytes as _sign_bytes
_signing = get_signing_identity() # <-- no remote_url
if _signing:
gpg_signature = _sign_bytes(_signing.private_key, content)
signed = True
except Exception as exc:
print(f"⚠️ Could not sign mist: {sanitize_display(str(exc))}", file=sys.stderr)
get_signing_identity(repo_root=None, remote_url=None, agent_id=None)'s docstring
states its resolution order falls back to "Hub URL from [hub] url in
.muse/config.toml" when remote_url is not supplied — i.e. without an explicit
remote_url, it can only resolve an identity if the current working directory
happens to be inside a repo with a locally configured hub. --hub (which the user
explicitly passed) is never threaded into this call at all — it's read into
hub_override and only used later, inside the separate if do_push: block, for
_require_hub(hub_override).
Confirmed live from /tmp (a directory with no .muse/config.toml):
>>> get_signing_identity()
None
>>> get_signing_identity(remote_url='https://staging.musehub.ai')
SigningIdentity(handle='gabriel', private_key=<...Ed25519PrivateKey...>)
Same identity, same machine, same keychain mnemonic — the only difference is
whether a hub URL is threaded through. The correct pattern already exists
elsewhere in the same file, in _hub_api (line 176):
get_signing_identity(remote_url=server_root).
Audited for the same anti-pattern across the whole muse/ tree —
grep -rn "get_signing_identity()" muse/ returns exactly one hit: this call
site. The bug is isolated, not systemic.
Why existing tests didn't catch this: test_M2_run_create_sign_no_import_error
in tests/test_mist_cli.py mocks get_signing_identity unconditionally
(mock.patch(..., return_value=fake_signing)), so it never observes what
arguments the real call site passes — it would pass even if the call were
get_signing_identity(), get_signing_identity(remote_url=None), or
get_signing_identity(remote_url="garbage"). No existing test asserts the hub
context is actually threaded through.
Goal
muse mist create --sign reliably produces a signed mist whenever any valid hub
context is resolvable — via --hub, via a repo-local .muse/config.toml, or
(when --push is also given) via --push's own hub resolution. In the one case
signing genuinely cannot resolve an identity (no --hub, no repo-local config, and
no --push to borrow a hub from), the CLI must print a clear, actionable warning
instead of silently returning "signed": false.
"Done" means: --sign --push --hub <url> signs correctly regardless of the
caller's current working directory, and --sign alone (no --push) still signs
when --hub is given or a repo-local hub is configured.
Out of Scope
- Signing behavior of other
mistsubcommands (fork,update,push) — this ticket is scoped tocreateonly, where the bug was found and reproduced. - Any broader refactor of
get_signing_identity's resolution order or the identity/keychain system itself — the fix is threading an already-correct pattern into one call site, not redesigning identity resolution.
Phases
Phase 1 — Red: reproduce the bug with a real (non-mocked) test
Existing coverage mocks get_signing_identity unconditionally, which is exactly
why this shipped unnoticed. The new test must exercise the real resolution path
end-to-end (real keychain-backed identity, real --hub flag, no mocking of
get_signing_identity itself) so it fails against current code and passes only
once the real argument-threading bug is fixed.
MISTSIGN_01—run_createwith--sign --hub <url>(no--push), invoked from atmp_pathdirectory with no.muse/config.tomlpresent, and a mocked keychain returning a known test mnemonic: assertsigned is Truein the JSON output. This must fail against current code (get_signing_identity()returnsNonebecause cwd has no hub config) and pass after the fix.MISTSIGN_02— mock-based assertion thatget_signing_identityis called withremote_urlequal to the resolved hub URL, for both--sign --push --hub <url>and--sign --hub <url>(no push). Asserts the call arguments, not just that signing eventually succeeds — this is what M2 should have asserted originally and didn't.MISTSIGN_03—--signwith no--huband no repo-local config and no--push: assertsigned is Falseand a clear warning is printed to stderr (e.g."Could not sign mist: no hub configured") — not silence.
Deliverable: ✅ all three tests written and confirmed red against pre-fix code
— MISTSIGN_01 got signed: False, MISTSIGN_02 got a call with no remote_url,
MISTSIGN_03 got silent stderr — before any Phase 2 code was written.
Phase 2 — Fix: thread hub context into the signing path
- Reused
_get_hub_url()(already non-raising — returnshub_url | Nonewithout_require_hub's hardSystemExit) rather than adding a new helper. run_create's signing block now resolveshub_urlvia:hub_override(the--hubflag) first, else_get_hub_url()'s repo-local lookup, elseNone. Passed asget_signing_identity(remote_url=hub_url).- When
do_pushis alsoTrue, the later_require_hub(hub_override)call resolves identically (samehub_override-first, same underlying_get_hub_url()fallback) — no divergent double-resolution. - Added a clear stderr warning ("no hub configured — pass --hub or run inside
a repo with a configured hub") on the
Nonecase, not just on exception.
Deliverable: ✅ MISTSIGN_01, MISTSIGN_02, MISTSIGN_03 all green.
Phase 3 — Regression sweep and audit
- Full
test_mist_cli.py— 95 passed, including pre-existingM1/M2/M3. grep -rn "get_signing_identity()" muse/— zero real call sites remain (one match is a comment in the fix referencing the historical bug, not a call).muse code test --jsonrun post-commit — reported "no changes detected" (comparison baseline already includes the committed fix).
Deliverable: ✅ MISTSIGN_04 — full regression pass, zero new failures, zero
remaining unscoped get_signing_identity() calls.
Acceptance Criteria
muse mist create <file> --sign --push --hub <url>produces"signed": trueregardless of the caller's current working directory.muse mist create <file> --sign --hub <url>(no--push) also produces"signed": trueunder the same condition.muse mist create <file> --signwith no hub context available anywhere prints a clear warning and returns"signed": false— never silent.- All of
MISTSIGN_01throughMISTSIGN_04pass; the fulltest_mist_cli.pysuite remains green. - No new zero-argument
get_signing_identity()call sites exist anywhere inmuse/.
Implementation Order
Phase 1 → Phase 2 → Phase 3. TDD by the book: the red tests in Phase 1 must be written and confirmed failing before any fix code in Phase 2 is written.