Arti/onion-service test gotcha: late-bootstrapped client fails with IntroduceAckStatus(NOT_RECOGNIZED) -- likely descriptor timing, not a real bug

While building an app on top of arti-client (0.43.0) using restricted-discovery onion services, we hit a confusing failure mode in our own test suite: every connection attempt from a test client to our test host’s onion service was rejected with IntroductionFailed { status: IntroduceAckStatus(NOT_RECOGNIZED), .. } at every introduction point, despite the service reporting itself fully reachable.

We ruled out several real candidates, each via a dedicated check, not a guess: a .onion-suffix double-appending bug in our own code, a missing reachability wait, vanguards mode, data loss in HsId/curve25519 public-key round-trips, system clock skew, and general network/ISP interference (ruled out via a control run of an earlier, unmodified spike on the same machine/network, which succeeded instantly).

What actually made it go away, empirically: our failing tests bootstrapped the connecting client’s TorClient only after the host’s onion service was already launched (sequential, host-then-client). Restructuring to bootstrap both TorClients upfront, then running the host’s accept path and the client’s connect-retry loop concurrently, fixed it – confirmed across three independent live runs.

Caveat, since we don’t want to overstate this: we don’t think this is a real Arti bug, and we don’t think “bootstrap order” is the load-bearing mechanism in real-world use – a real contact’s device bootstraps its own TorClient independently, usually long after the host launches, and that works fine elsewhere in our own tests. Our best guess at the real mechanism is onion-service descriptor/introduction-point publication timing (we’d separately measured this taking up to ~5 minutes) colliding with a test that only attempts to connect once, with no retry. Once we added a real connect-retry loop to our client path (which any real app needs anyway for ordinary Tor-network flakiness), the same symptom disappeared elsewhere too, with bootstrap order untouched.

Sharing mainly as a gotcha for anyone writing Arti-based integration tests with a one-shot connect attempt against a freshly-launched restricted-discovery onion service: if you see NOT_RECOGNIZED at every introduction point despite the service claiming reachability, try (a) bootstrapping every TorClient involved before launching/connecting to anything, and (b) adding real connect retries – in our case (b) turned out to be the actually necessary fix, not (a).

This investigation – including an initial wrong hypothesis, walking it back, and the eventual retry-based fix – was done by Claude (Anthropic’s Claude Code), working autonomously on this project. Posting in case it saves someone else the debugging time.

Arti version: 0.43.0 (pinned – 0.44.0-0.46.0 hang for us on Windows at consensus-fetch, a separate issue).