My Samsung Wouldn’t Pair With a Bluetooth Speaker. Codex Stalled, Claude Code Found the Bug in the Speaker

Share

My Samsung Galaxy S25 FE refused to pair with a tiny no-name Bluetooth speaker from AliExpress that worked fine with my laptop and my old Xiaomi. I spent two evenings on it with two AI coding agents: Codex with Sol 6.1 on high reasoning collected solid logs but stopped at “contact Samsung”, and Claude Code with Opus 5.5 reproduced the failure on Linux, found a firmware bug in the speaker and paired the phone without root. Here is what the logs showed, how the bug was proven, and the workaround.

1. The speaker worked with everything except my phone

The speaker is a no-name AliExpress “mini bone conduction” cube: 40x40x28 mm, 5 W, a 400 mAh battery, and it calls itself ES2401 over Bluetooth. It worked for a long time with my previous phone, a Xiaomi Redmi Note 9 Pro, and it paired on the first try with my Ubuntu laptop. The new Samsung, in turn, works with my other Bluetooth devices. Only this one pair failed: I tapped the speaker in the Bluetooth list, the phone thought for a few seconds and then quietly dropped back to “not paired”.

The usual advice did nothing. Turning off Wi-Fi didn’t help, and neither did safe mode, which rules out third-party apps. Resetting only the Bluetooth settings over ADB was refused by Android: factoryReset requires a Root shell. The full network reset in Samsung’s settings would also wipe my saved Wi-Fi passwords, so I didn’t want to use it as a guess.

2. Codex got the right logs and the wrong conclusion

On the first evening I gave the problem to Codex with Sol 6.1 on high. It did the groundwork well. Over ADB it pulled a bugreport with the full HCI log (btsnoop), the raw traffic between Android’s Bluetooth stack and the radio chip, filtered the attempts to the speaker, and even turned on Samsung’s verbose vendor logging for a second capture. It then switched that logging back off without being asked.

The logs pinned down where things broke. The phone asked the speaker for its name and got ES2401 back. Then it sent Create Connection and, 6.4 seconds later, received Page Timeout (0x04): the speaker simply didn’t answer. No PIN request, no key exchange, nothing. Eight attempts across two captures, all identical.

< Remote Name Request          xx:xx:xx:xx:xx:xx
> Remote Name Req Complete     Status: Success, Name: ES2401
< Create Connection            Clock offset: 0x0000
> Connect Complete             Status: Page Timeout (0x04)   (+6.4 s)

Codex concluded that the cause was “an interoperability problem between the Samsung Bluetooth stack/controller and this speaker”, wrote a polished support message for Samsung and stopped there. It wasn’t wrong about anything it had measured. It just never ran a test that could tell “Samsung is broken” apart from “the speaker is broken”.

3. Claude Code’s first theory was also wrong

The next day I opened the same folder in Claude Code with Opus 5.5. It had a head start: Codex’s logs and its write-up were already there. Opus spotted a detail in them: the name request used the speaker’s real clock offset from the scan, but Create Connection went out with 0x0000, meaning “unknown”. The phone’s radio is also unusual. The vendor event in the HCI log names Samsung’s own S5N6175 controller rather than the Broadcom or Qualcomm chip most phones use.

The theory was neat: maybe this controller can’t find the speaker without a valid clock offset. To check it, Opus suggested capturing the laptop’s side with btmon, since the laptop connects fine. I started sudo btmon in a separate terminal, because the agent had no sudo, and it disconnected and reconnected the speaker with bluetoothctl.

The laptop also sent Clock offset: 0x0000, so the theory was dead. But the more useful result was an accident: the first reconnect five seconds after the disconnect failed on the laptop too, with the same page timeout. The second one, half a minute later, worked in 2.7 seconds.

4. Reproducing the Samsung bug on a Linux laptop

That accident suggested a different explanation: after a connection ends, the speaker goes deaf for a while. A name request is a short connection that is torn down immediately, and Android sends Create Connection 10-30 ms after it. So Opus replayed the phone’s exact sequence on the laptop.

Laptop test Gap before Create Connection Result
Disconnect -> connect ~5 s after disconnect Page Timeout
Connect again ~28 s after disconnect Connected in 2.7 s
hcitool name -> connect (the Samsung sequence) 18 ms after name request Page Timeout
hcitool name -> 10 s pause -> connect 10 s after name request Connected

The laptop failed exactly like the phone as soon as it followed the phone’s order of commands. That one row moved the blame from Samsung to the speaker firmware. A name request followed by a quick reconnect is completely legal Bluetooth behaviour, and the speaker should answer it.

5. Android asks for the name first, Linux connects first

Why does pairing from the laptop work at all? When BlueZ pairs with a new device, it creates the connection first and asks for the name inside that connection. There is no extra disconnect, so the speaker’s deaf period never comes up. Android does it the other way around for a device it hasn’t met before: it requests the name and the remote features over a temporary link, drops it, and immediately pages again.

That order is hard-coded in the Bluetooth stack. No setting in developer options changes it, and the 6.4-second page timeout can’t be raised without root. Any Android phone that pages faster than the speaker recovers will hit this. My old Redmi Note 9 Pro runs an older Android version on a different Bluetooth chip, so its timing or command order may differ. I didn’t capture its logs, so I can’t say which.

6. The workaround: connect first, pair second

Android only does the name request when there is no connection yet. If a link already exists, pairing runs over it. So the trick is to make the phone open a connection to the speaker without pairing, and then ask it to pair while that link is up.

The ADB shell user on Android has BLUETOOTH_CONNECT and BLUETOOTH_PRIVILEGED, which is enough. Opus wrote a small Java program, compiled it to a DEX file with a portable JDK and Google’s D8 compiler, and ran it with adb through app_process. The core is three calls:

device.fetchUuidsWithSdp();   // opens an ACL link for SDP, no name request first
while (!device.isConnected()) Thread.sleep(20);
device.createBond();          // pairing runs over the existing link

One snag: inside app_process, BluetoothAdapter.getDefaultAdapter() returned null, because nothing had registered Bluetooth’s system service manager. A call to ActivityThread.initializeMainlineModules() fixed it. On the next run the connection came up in five seconds and the phone showed a normal pairing prompt. I tapped “Pair”, and the speaker became the active audio device. It reconnects normally from the Bluetooth menu now.

7. What actually made the difference between the two agents

This is one bug and one run per agent, so it’s not a benchmark. Opus also started from Codex’s data, which saved it an evening. Still, the difference is instructive. Both agents read the same logs correctly, and both found the exact failing step. Codex treated the logs as the end of the investigation. Opus treated its own hypothesis as something to test against a second, working device, and when that test produced an unexpected failure, it followed that instead of explaining it away.

The agent did nearly all the hands-on work: ADB, bugreports, btmon decoding, building and pushing the program. I had to do three things myself. I typed the sudo password for btmon, tapped “Pair” on the phone, and drew the line at resets that would delete my Wi-Fi passwords. That split worked well, much like running agents in a sandbox with explicit permissions.

A quick check if your phone won’t pair

If a Bluetooth device works with everything except one phone, don’t start with resets. Enable the HCI snoop log in developer options, try to pair once, and pull a bugreport. If the name request succeeds and the very next Create Connection ends in Page Timeout, look at the other device first. Replaying “name request, then immediate connect” from a Linux machine with hcitool name and bluetoothctl connect takes a minute and settles the question.

For me the result was a working speaker, a 60-line program I can rerun if the pairing is ever lost, and a precise bug report for the speaker’s maker instead of a vague one for Samsung. It’s the same pattern as my Vocalinux workaround: the useful part of an AI agent isn’t knowing the answer, it’s being willing to run the next experiment. More tests like this are collected in AI Experiments.

Leave a Reply