●CODE — Claude Code adds Trusted Devices, verifying a machine before remote admin sessions begin●CODE — CPU use drops about 37% during streaming, keeping long always-on automation steadier●CODE — Fullscreen mouse-click controls, voice dictation fixes, and better Linux voice detection land●AUTH — Static API keys can now be replaced with short-lived, scoped WIF credentials●TEAM — You can tag Claude directly in Slack and delegate tasks while you focus elsewhere●WORKFLOW — Dynamic workflows arrive in research preview, breaking complex work into steps on their own●CODE — Claude Code adds Trusted Devices, verifying a machine before remote admin sessions begin●CODE — CPU use drops about 37% during streaming, keeping long always-on automation steadier●CODE — Fullscreen mouse-click controls, voice dictation fixes, and better Linux voice detection land●AUTH — Static API keys can now be replaced with short-lived, scoped WIF credentials●TEAM — You can tag Claude directly in Slack and delegate tasks while you focus elsewhere●WORKFLOW — Dynamic workflows arrive in research preview, breaking complex work into steps on their own
Borrowing the Trusted Devices Idea for a Solo Setup: Pinning Automated Runs to Devices You Approved
The Trusted Devices feature that landed in Claude Code on June 28, 2026 is for Team and Enterprise. You can't use the feature on a solo plan, but you can borrow the idea. Here is how to identify a device in a way that doesn't break, and stop any automated run that starts from a device you never approved — with working code and the traps to avoid.
The scariest moment in scheduled automation isn't when a job fails. It's when a job succeeds quietly on a device you never meant to run it on. As an indie developer I run article updates for several sites unattended, staggered through the night, and besides my main Mac I keep a second machine for working sessions. The same tokens and config sometimes sync to both. When that happens, "the same job ran twice in the middle of the night, once from a machine that was never supposed to touch it" is exactly the kind of incident that goes unnoticed for too long — because nobody is watching a screen.
On June 28, 2026, Claude Code gained Trusted Devices: a Team / Enterprise capability that verifies the device itself before a remote Claude Code session begins. You can't use it on a personal plan, but the underlying idea — making "which device is this running on" the starting point of your operations — is especially useful for a small, solo automation. Below I turn that idea into code so that a run never even starts from a device you haven't approved.
What you're protecting is the device, not the model
When people think about security for unattended runs, attention goes to key strength and permission scopes. Those matter. But in solo development the thing that actually bit me was more mundane: a drift in who is executing.
Concretely: an old laptop I never wiped still had a synced cron entry quietly firing. A VM I cloned "just for testing" inherited the production schedule. A script in a cloud-synced folder got mounted on a machine I didn't expect. None of these are "unauthorized access." They are all the same problem — my own asset running somewhere I didn't intend.
That is exactly the axis Trusted Devices points at. The question isn't whether something holds the key; it's whether the operator knew, in advance, which device is using it. Translated to a solo setup, it becomes a minimal gate: keep your own list of approved devices, and refuse to read credentials anywhere else.
What Trusted Devices covers, and the gap left for solo runs
First, let's be clear about the scope difference, so we know which part to build ourselves.
Aspect
Trusted Devices (Team / Enterprise)
What you build for solo
What is verified
The device starting a remote session
The device where cron / launchd runs
Allowlist management
Admin approves in a console
You keep one file of approved devices
On rejection
Blocks the session from starting
Exit before reading credentials, log and alert
Scope
Mostly interactive / remote
Fully unattended batch jobs
What a solo operator really wants protected is the batch entry point, not the interactive one. A nightly scheduled run is not a remote session, so it falls outside Trusted Devices. That's precisely why it's worth carrying the same idea as your own preflight, right before execution.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦How to pick a device identifier that survives reboots and network changes (macOS IOPlatformUUID / Linux machine-id) instead of fragile hostname or IP, with the code to read it on both
✦A preflight guard you call from cron or launchd that refuses to read credentials at all unless the device is on your allowlist
✦Why a blocked run must never exit quietly: logging and alerting so a new, un-approved device surfaces instead of silently doing nothing
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Identify the device uniquely, and in a way that won't break
The foundation of the gate is "how do you identify a device." Get this wrong and you reject your own legitimate machine and stall the whole pipeline.
Here's the conclusion up front: do not use hostname, IP address, or MAC address as the identifier. I built it on those first, and switching Wi-Fi alone got my own Mac rejected — I burned the better part of an hour finding out why. Those values describe where a device is, not which device it is.
Use the stable hardware / install identifiers the OS already exposes instead.
Identifier
Stability
Verdict
hostname
User can change it anytime
Don't use
IP / MAC address
Shifts with network and randomization
Don't use
macOS: IOPlatformUUID
Tied to hardware, stable
Use
Linux: /etc/machine-id
Stable per OS install
Use
Wrap both OSes behind one thin function so the rest of the code doesn't care.
#!/usr/bin/env bash# device_id.sh — print a stable device identifier (macOS / Linux)set -euo pipefaildevice_id() { case "$(uname -s)" in Darwin) # IOPlatformUUID is tied to the hardware; survives reboots and network changes ioreg -d2 -c IOPlatformExpertDevice \ | awk -F\" '/IOPlatformUUID/{print $4; exit}' ;; Linux) # machine-id is stable per OS install; check systemd and non-systemd locations cat /etc/machine-id 2>/dev/null || cat /var/lib/dbus/machine-id ;; *) echo "unsupported-os" >&2 return 1 ;; esac}device_id
You can put the raw UUID straight into the allowlist, but I fold it into a short hash first. If I ever share the list by mistake, I haven't leaked the raw device UUID.
# Fold the raw UUID into the first 12 chars of a SHA-256 (this is what goes in the list)short_id() { device_id | shasum -a 256 | cut -c1-12; }
A preflight guard that only lets approved devices run
Once the identifier is stable, the gate itself is tiny. Keep the allowlist as one device per line, and route every real job through this guard first.
#!/usr/bin/env bash# preflight_device_guard.sh — stop before reading credentials on un-approved devicesset -euo pipefailHERE="$(cd "$(dirname "$0")" && pwd)"source "$HERE/device_id.sh"ALLOWLIST="$HERE/allowed_devices.txt" # short_id + optional label, one per lineID="$(short_id)"# Skip comments (#) and blank lines; match the first token onlyif ! awk 'NF && $1!~/^#/{print $1}' "$ALLOWLIST" | grep -qx "$ID"; then echo "BLOCKED: device $ID is not on the allowlist" >&2 exit 70 # exit here, before any credential is readfiecho "OK: trusted device $ID"
The allowlist can stay this plain:
# allowed_devices.txt — only the devices you approveda1b2c3d4e5f6 main-mac-studio # production posting runs from this device9f8e7d6c5b4a backup-mini # standby for incidents
The key point is to place the guard not as the first step of the job, but as the step before credentials are read. Checking after you've already expanded the token into an environment variable is much weaker — the key is already in memory. Keep the order strict: verify device, then read credentials, then do the work.
From cron or launchd, don't call the real script directly — call it through the guard.
#!/usr/bin/env bash# run_daily_job.sh — the scheduler invokes thisset -euo pipefailHERE="$(cd "$(dirname "$0")" && pwd)"# If the device isn't approved, this stops here with exit 70"$HERE/preflight_device_guard.sh"# Only an approved device reaches this pointsource "$HERE/load_credentials.sh" # read credentials only after the device checkexec "$HERE/post_articles.sh"
Bind the credential to the device
The guard alone already prevents most "oops, it ran on the wrong machine" incidents. But since someone could copy the files and strip the guard, it's worth binding the credential itself to the device.
A solid approach is to never store the API key in plaintext, but decrypt it with a key derived from the device identifier. The loader below makes the plaintext key obtainable only on an approved device. Copy the whole thing to another machine and decryption fails, because short_id won't match.
#!/usr/bin/env python3# load_credentials.py — decrypt the API key bound to this deviceimport base64, hashlib, hmac, os, subprocess, sysdef short_id() -> str: out = subprocess.check_output( ["bash", os.path.join(os.path.dirname(__file__), "device_id.sh")] ).strip() return hashlib.sha256(out).hexdigest()[:12]def _stream_key(device_short_id: str, salt: bytes, length: int) -> bytes: # Deterministically derive a keystream from device id + salt (no extra deps) seed = device_short_id.encode() out, counter = b"", 0 while len(out) < length: out += hmac.new(seed, salt + counter.to_bytes(4, "big"), hashlib.sha256).digest() counter += 1 return out[:length]def load_api_key(enc_path: str) -> str: raw = base64.b64decode(open(enc_path, "rb").read()) salt, payload = raw[:16], raw[16:] stream = _stream_key(short_id(), salt, len(payload)) plain = bytes(a ^ b for a, b in zip(payload, stream)) if not plain.startswith(b"sk-"): # wrong device -> garbage decryption, caught here print("BLOCKED: credential is not bound to this device", file=sys.stderr) sys.exit(70) return plain.decode()if __name__ == "__main__": os.environ["ANTHROPIC_API_KEY"] = load_api_key(sys.argv[1]) print("credential loaded for this device")
The sk- prefix check here is a cheap sanity test that decryption actually worked. Run the copy on another machine and short_id changes, the decryption is garbage, the prefix won't match, and you stop before the plaintext key is ever in memory. For something stricter, swap in authenticated encryption (AES-GCM via cryptography); I used only the standard library here to show the mechanism without adding dependencies.
In practice, build a separate encrypted file per approved device. When you approve a new device, run the encryption script once on that device to produce its *.enc. Key generation and device approval then line up one-to-one naturally.
Don't let a rejection end in silence
This is the part that hurt me most. When I first added the guard, a rejection just exited quietly. Then I reinstalled the OS on my main Mac, the identifier changed, and every nightly job started "succeeding" by doing nothing — updates were stalled for two days before anyone noticed.
A rejection is a failure, and failures must surface somewhere visible. Extend the guard to log and alert when it blocks.
# replace the tail of preflight_device_guard.shif ! awk 'NF && $1!~/^#/{print $1}' "$ALLOWLIST" | grep -qx "$ID"; then MSG="BLOCKED device=$ID host=$(hostname) at $(date -u +%FT%TZ)" echo "$MSG" >> "$HERE/device_guard.log" # one webhook is enough (keep the URL in an env var) if [ -n "${ALERT_WEBHOOK:-}" ]; then curl -fsS -X POST "$ALERT_WEBHOOK" \ -H 'Content-Type: application/json' \ -d "{\"text\":\"$MSG\"}" >/dev/null || true fi exit 70fi
The destination doesn't matter much; what matters is being able to tell "blocked" apart from "succeeded and did nothing." Using a dedicated exit code like 70 instead of 0 lets even the scheduler's own logs show at a glance that this was a block, not a clean finish.
Traps you will hit (reinstall, VM clone, CI)
Three traps you'll almost certainly run into.
OS reinstalls or major upgrades can change Linux's machine-id. macOS's IOPlatformUUID generally persists, but it changes on a logic-board swap. Bake "update the allowlist when the identifier changes" into your procedure from the start. I keep that step as a one-line script (print the current short_id to copy) on my post-reinstall checklist.
Cloning a VM or container can clone the machine-id along with it. If a cloned test environment claims the same identifier as production, it slips past the guard. If you spin VMs from a template, always regenerate machine-id on first boot (on systemd, blank it and let it regenerate).
If you want the same job to run in CI, the runner's identifier changes every time, so this guard won't pass as-is. In CI, protect along a different axis — short-lived OIDC-based credentials, for example — instead of device pinning. Accepting that device pinning fits "automation that runs on a fixed device you physically control" keeps the scope clear and avoids confusion.
A first step
Tonight, wrap just one job with the preflight_device_guard.sh above. The allowlist needs exactly one line: the short_id of the device you're running on right now. Device-bound credentials and encryption can come later, when you actually need them. Even just writing down "which device do I run this from" turns out to clear up an unattended setup more than you'd expect. For me, simply making that explicit lifted a vague, persistent unease about what runs in the dark.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.