90 Seconds of Nothing: Fixing a TPM Boot Stall and Building a Learn-First Claude Code Setup on Omarchy

A kernel update left my Omarchy laptop waiting 90 seconds on every boot for a TPM that would never answer. I traced it through systemd's critical chain, fixed it with one kernel parameter, and cut userspace boot from 1m38s to 5.3s. Then I set up Claude Code to explain every command before it runs, so I stop copying commands I don't understand and start learning Black Hat Python the right way.

Lab date: Sunday, September 20, 2026

Part 1: Post-update health check (Omarchy, HP laptop)

After updating my Omarchy laptop to kernel 7.2.3-arch1-3, I ran a structured health check:

CommandWhat it told me
uname -r / pacman -Q linuxThe running kernel matched the installed package, so the update took.
systemctl --failed0 failed services.
journalctl -p 3 -xbError-level messages from this boot (-p 3 = errors, -b = this boot, -x = explanations).

Most of the journal turned out to be harmless noise: SGX/TDX (CPU features my hardware doesn’t offer), lis3lv02d (HP’s drop sensor), ACPI BIOS Error ... WMID (HP firmware bugs), and wpa_supplicant notices. One line mattered: MSFT0101 ... failed to claim resource ... -16. My TPM chip couldn’t start because of a firmware resource conflict.

Part 2: The 90-second boot delay

The investigation:

  • systemd-analyze showed userspace taking 1min 38s.
  • systemd-analyze blame showed nothing slow. That puzzled me until I learned that a wait isn’t a service, so it never shows up there.
  • systemd-analyze critical-chain found the culprit: tpm2.target → sysinit.target → everything else. Systemd was waiting 90 seconds for a TPM device that never appeared, and nearly every service on my system was waiting with it.

[SCREENSHOT: systemd-analyze before — 2min 1.986s total]

[SCREENSHOT: critical-chain output showing tpm2.target]

Safety checks before changing anything:

My disk is LUKS-encrypted, so before touching the TPM I needed to be sure my disk unlock didn’t depend on it.

  • cat /proc/cmdline showed cryptdevice=, which means Arch’s classic encrypt hook: passphrase-only unlock, no TPM involved.
  • sudo cat /etc/crypttab contained only comments, so no other volumes depended on the TPM either.

The fix:

  1. sudo cp /etc/default/limine /etc/default/limine.bak — I made a backup first.
  2. I appended KERNEL_CMDLINE[default]+=" systemd.tpm2_wait=0" to the file with tee -a.
  3. sudo limine-update rebuilt the boot images for both of my kernels (stock linux and linux-omarchy).
  4. grep confirmed the parameter was in /boot/limine.conf before I rebooted.

The lesson from a stale result: my first check after the fix showed identical numbers, down to the millisecond. uptime and journalctl --list-boots proved I hadn’t actually rebooted yet. After a real reboot, userspace went from 1min 38s to 5.3s, with no TPM timeouts.

[SCREENSHOT: systemd-analyze after — 5.290s userspace]

The lesson about which clock I was reading: the “kernel” time then showed 2min 58s, which briefly worried me. journalctl -o short-monotonic misled me, because it shows when journald received each message, and journald doesn’t run inside my initramfs. sudo dmesg showed the kernel’s own timestamps: device-mapper ready at 2.7s, root mounted at 178s. That gap was my LUKS passphrase screen sitting there waiting for me, not a system problem. The machine itself boots in about 18 seconds plus however long I take to type my passphrase.

[SCREENSHOT: dmesg lines showing the 2.7s → 178s passphrase gap]

Part 3: Setting up Claude Code on Omarchy

  • Installation: running claude --version triggered mise (Omarchy’s tool manager) to install Claude Code 2.1.278 on the spot, because my ~/.config/mise/config.toml already listed it.
  • Login: Sonnet 5 · Claude Pro.
  • Permission mode: I set it to Manual through /config and confirmed it saved after a restart (⏸ manual mode on).
  • Output style: Learning. I caught that typing Learning without the / only produced Claude saying the style had changed, while /output-style itself still showed default (current). The real command, /output-style Learning, gave output marked with ⎿, which is how Claude Code shows actual tool output, and /output-style then confirmed Learning (current).
  • Standing instructions: I created ~/.claude/CLAUDE.md telling Claude Code to explain every command, flag, and system change before running anything. I used a cat >> ... << 'EOF' heredoc to write it, and /memory confirmed it loaded as User instructions.

[SCREENSHOT: /output-style showing Learning (current)]

How I read permission prompts now:

  • Yes means once. I’m sticking with this while I learn.
  • “Don’t ask again for … *“ — the * is a wildcard that allows anything matching. I avoid those.
  • “Switch to auto mode” would undo Manual mode. I ignore it.
  • “Accept edits” lets file changes go through without asking. I skip it for now.
  • The faint text in the input box is only a suggestion. Tab accepts it, and nothing is sent until I press Enter.

[SCREENSHOT: Claude Code’s first permission prompt, with its explanation above it]

Part 4: My Black Hat Python workspace

mkdir -p ~/projects/bhp && cd ~/projects/bhp
python -m venv .venv
source .venv/bin/activate
  • I verified the venv with echo $VIRTUAL_ENV and which python, both pointing into .venv. Omarchy’s prompt doesn’t display (.venv), so those two checks are my indicator.
  • I installed paramiko 5.0.0 (SSH) and scapy 2.7.0 (packets), plus their transitive dependencies (cryptography, bcrypt, PyNaCl, cffi, pycparser, invoke).
  • requirements.txt records the pinned versions. To rebuild the environment anywhere: pip install -r requirements.txt.

Shell concepts I picked up: ; runs the next command no matter what, while && runs it only if the previous command succeeded. > overwrites a file and >> appends to it. ~ means my home folder. $VAR means a variable’s value.

My Neovim escape hatch: Esc, then :q!, then Enter quits without saving.

Other tips I’m keeping: PrtSc takes a screenshot, Super+K lists Omarchy’s shortcuts, Ctrl+L redraws a scrambled Claude Code screen, and Ctrl+O shows the full command output behind “Ran 1 shell command.”

Next up: Chapter 2

How I pick up where I left off:

cd ~/projects/bhp
source .venv/bin/activate
claude --continue

--continue reopens my most recent Claude Code conversation in that folder, so it remembers where I stopped.

Where I am: Chapter 2, the TCP client, starting with import socket. Next comes creating the socket with AF_INET (IPv4) and SOCK_STREAM (TCP), then connect, send, and recv. After the client, the chapter moves on to a TCP server, a UDP client, and eventually a netcat replacement and SSH with paramiko.

Habits I’m keeping:

  • Type every line myself, and compare it with the book.
  • Verify Claude’s claims with the tool itself, not just its summary.
  • Say Yes once, and avoid wildcards.
  • Scapy sniffing will need sudo. I’ll set that up properly when I reach those chapters.

Too many times I’ve copied commands without understanding them. This setup makes me slow down and learn what each one actually does — and that’s the whole point.