Claudony — Zero Configuration

What I was trying to achieve: close the last deployment blocker

The previous entry ended on one open item: how does the agent API key get provisioned on a fresh Mac Mini? Without it, server and agent can’t authenticate with each other and nothing works.

I wanted zero manual setup for the common case — same machine, development mode. That meant auto-generation.

What I believed going in: the design was clear, the implementation would be routine

The resolution order came together quickly in design: check the config property first (explicit always wins), then check ~/.claudony/api-key (same-machine auto-discovery), then generate if absent. Server generates, agent reads or warns. New ApiKeyService bean, injected wherever the key is needed.

The constraint that made this interesting: Quarkus config resolution is immutable after startup. You can’t inject a runtime-generated key back into the config system. ApiKeyService has to sit outside it and serve the resolved key at request time.

The timing trap buried in the auth wiring

We built ApiKeyService as a pure unit test first — @TempDir, mocked ClaudonyConfig, same pattern as CredentialStore. Eight tests covering the resolution branches, file permissions (600), blank-file handling, config precedence. A code quality review caught one real issue before commit: persistKey() was void and always set resolvedKey, so a failed write would still trigger the first-run banner claiming the key was saved. Fixed to return boolean; the banner only fires if persistence succeeded.

The more interesting catch came when wiring ApiKeyService into ApiKeyAuthMechanism. The implementer had added a @PostConstruct autoInit() beyond what the task specified. The reason: in @QuarkusTest, HttpAuthenticationMechanism implementations process requests before @Observes StartupEvent fires.

ApiKeyAuthMechanism calls apiKeyService.getKey() at request time. initServer() — which sets resolvedKey — is called from ServerStartup.onStart(), a StartupEvent observer. First test request arrives, resolvedKey is still empty, every authenticated call returns 401.

The fix: @PostConstruct that loads the config key at CDI construction time, before any observers fire. No file I/O — just loadFromConfig(). When initServer() runs later, it re-evaluates from the top, finds the already-set key, returns early. Idempotent. Nothing in the Quarkus docs warns about this ordering.

First boot

On first server run with no key configured:

================================================================
CLAUDONY — API Key Generated (first run)
  Key:      claudony-550e8400e29b41d4a716446655440000
  Saved to: /Users/you/.claudony/api-key

  Same machine (agent + server co-located): no action needed.
  Different machine: configure the agent with —
    export CLAUDONY_AGENT_API_KEY=claudony-550e8400...
================================================================

Agent on the same machine finds the file at startup and loads silently. Agent on a different machine starts degraded with a matching warning and the exact env var to set.

124 tests passing. The Mac Mini gets the binary, launchd starts it, and the key provisions itself on first boot.

← Claudony — Two Silent Bugs and a Working Binary Claudony — The Compose Took Five Tries →