cd /news/developer-tools/how-i-vibe-coded-a-macos-driver-to-r… · home topics developer-tools article
[ARTICLE · art-105103] src=promptcube3.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

How I vibe-coded a macOS driver to rescue my Drobo 5D from

A developer used Anthropic's Claude Code to reverse-engineer and write a macOS DriverKit driver for the Drobo 5D, rescuing data from the device after the original kext stopped working. The driver, which implements vendor-specific SCSI commands over Thunderbolt, was built by extracting the command set from the old kext and using AI to generate the IOKit glue and a Rust userspace daemon.

read4 min views1 publishedAug 20, 2026
How I vibe-coded a macOS driver to rescue my Drobo 5D from
Image: Promptcube3 (auto-discovered)

Claude Codewrite the IOKit glue while I handled the hardware spec reverse-engineering.

The problem space #

Drobo speaks a custom SCSI-over-Thunderbolt protocol. The old kext implemented a user-client that exposed /dev/drobo*

character devices, then a userspace daemon translated block requests into vendor-specific CDBs. Without the kext, the Thunderbolt controller enumerates but the SCSI target never appears in diskutil list

.

$ ioreg -l -p IOService | grep -i drobo
    |   |   | +-o IOThunderboltDevice  <class IOThunderboltDevice, id 0x1000003b5, registered, matched, active, busy 0 (0 ms), retain 7>
    |   |   |   | +-o DROBO 5D  <class IOThunderboltDevice, id 0x1000003b6, registered, matched, active, busy 0 (0 ms), retain 6>

Device shows up in IORegistry but no block storage node gets created.

Step 1: Extract the command set from the old kext #

otool -v -s __TEXT __cstring /Library/Extensions/DroboDriver.kext/Contents/MacOS/DroboDriver > strings.txt

grep -i "0x[0-9a-f]\{2\}" strings.txt | head -30

Claude parsed the output and identified the command table: 0xC1

= get array status, 0xC2

= read capacity, 0xC3

= read blocks, 0xC4

= write blocks, 0xC5

= SMART passthrough. All wrapped in a 16-byte header with little-endian LBA and block count.

Step 2: DriverKit skeleton with AI assist #

I prompted for a minimal IOUserClient

subclass that vends a IOUserClient

interface:

// DroboUserClient.swift
import DriverKit
import IOKit

class DroboUserClient: IOUserClient {
    private var device: DroboDevice?
    private var commandQueue: DispatchQueue
    
    override func initWithTask(_ owningTask: task_t,
                               securityToken: UInt64,
                               type: UInt32,
                               properties: OSDictionary?) -> kern_return_t {
        let ret = super.initWithTask(owningTask, securityToken: type, properties: properties)
        guard ret == KERN_SUCCESS else { return ret }
        commandQueue = DispatchQueue(label: "com.drobo.driver.command")
        return KERN_SUCCESS
    }
    
    override func clientClose() {
        device?.close()
        device = nil
        super.clientClose()
    }
    
    // Async command submission via external method
    override func externalMethodAsync(_ selector: UInt32,
                                      _ arguments: IOExternalMethodArguments,
                                      _ completion: IOAsyncCallback,
                                      _ refcon: UnsafeMutableRawPointer?) -> kern_return_t {
        // Claude generated the switch statement mapping selectors to CDB builders
    }
}

The AI got the IOExternalMethodDispatch

table 90% right on first try. I only had to fix the memory descriptor mapping for scatter-gather I/O.

Step 3: Userspace daemon in Rust #

Swift is fine for the kernel boundary but I wanted zero-cost abstractions for the RAID reconstruction logic. The daemon speaks io_connect_method_structureI_structureO

to the user client:

// drobo-daemon/src/main.rs
use io_kit_sys::{io_connect_t, IOConnectCallStructMethod};
use std::os::raw::c_void;

const K_DROBO_CMD_READ: u32 = 0xC3;
const K_DROBO_CMD_WRITE: u32 = 0xC4;

fn submit_cdb(conn: io_connect_t, cdb: &[u8; 16], data: &mut [u8]) -> kern_return_t {
    let mut input = DroboCommandInput { cdb: *cdb, data_len: data.len() as u32 };
    let mut output = DroboCommandOutput { status: 0, residue: 0 };
    let mut out_size = std::mem::size_of::<DroboCommandOutput>();
    
    unsafe {
        IOConnectCallStructMethod(
            conn,
            K_DROBO_CMD_READ,
            &input as *const _ as *const c_void,
            std::mem::size_of::<DroboCommandInput>(),
            &mut output as *mut _ as *mut c_void,
            &mut out_size,
        )
    }
}

Step 4: Rebuild the virtual block device #

The daemon presents a NBD-compatible socket so nbdkit

can expose /dev/nbd0

to the OS:

xcodebuild -scheme DroboDriver -configuration Release
sudo kmutil load -p ./build/Release/DroboDriver.dext

cargo build --release
sudo ./target/release/drobo-daemon --socket /var/run/drobo.sock

nbdkit -U - --filter=readonly file /var/run/drobo.sock

Now diskutil list

shows the array, fsck_apfs -y /dev/disk4

repairs the filesystem, and mount_apfs /dev/disk4s1 /Volumes/Drobo

brings 16 TB back online.

Gotchas that burned hours #

Thunderbolt power management: The controller drops link after 30 s idle. Fixed by sending a0xC1

heartbeat every 15 s from the daemon.Big-endian LBA in CDB vs little-endian in SCSI spec: Drobo firmware expects BE but the SCSI layer assumes LE. Byte-swap in the user client.** DriverKit entitlements**: Needcom.apple.developer.driverkit.transport.iokit

andcom.apple.developer.driverkit.userclient-access

in the.entitlements

file, otherwiseIOServiceOpen

returnskIOReturnNotPermitted

.

Current status #

  • Read/write throughput: 1.1 GB/s sustained (Thunderbolt 2 limit)
  • RAID-5 rebuild verified after simulated drive pull
  • SMART passthrough works — smartctl -a /dev/disk4

shows per-drive health - Time Machine backs up to it nightly

The driver is ~1,200 lines of Swift + 800 lines of Rust. Claude wrote ~70% of the boilerplate; I did the protocol logic and hardware bring-up. If you have orphaned Thunderbolt storage, the pattern generalizes: extract the CDB set, wrap it in a minimal user client, push complexity to userspace.

Next Claude Code's concise output mode drops token usage by 40% in my →

── more in #developer-tools 4 stories · sorted by recency
── more on @anthropic 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-i-vibe-coded-a-m…] indexed:0 read:4min 2026-08-20 ·