In meiner Diktier-App VoiceFlow gibt es einen „Profi-Modus": Sprache rein, sauberer Text raus. Die Politur — Füllwörter weg, Satzbau geglättet — soll ein gutes Modell machen. Lokale LLMs waren auf meiner GPU zu langsam oder zu schwach. Naheliegend wäre die Anthropic-API gewesen. Aber ich habe ohnehin ein Claude-Abo, in dem ich eingeloggt bin. Warum dafür extra einen API-Key buchen und doppelt zahlen?
Die Antwort: claude -p headless als Subprozess. Die CLI nutzt die bestehende Abo-Anmeldung. Kein ANTHROPIC_API_KEY, keine zweite Abrechnung. Hier ist, was dabei alles schiefging und wie der Aufruf am Ende aussieht.
Der nackte Aufruf
cmd = [
self.exe,
"-p",
"--model", "claude-haiku-4-5",
"--max-turns", "1",
"--system-prompt", system_prompt,
"--strict-mcp-config",
"--tools", "",
"--no-session-persistence",
]
Jeder Flag verdient eine Begründung:
--max-turns 1— ich will eine Transformation, keinen Agenten-Loop. Ein Turn, fertig.--system-promptersetzt den Default-Prompt komplett, statt anzuhängen. Das ist genau, was ich will: kein Claude-Code-System-Framing, nur meine Politur-Anweisung.--strict-mcp-configplus--tools ""— keine MCP-Server, keine Tools. Es gibt nichts zu laden, der Start ist schneller.--no-session-persistence— schreibt keine Sitzungsdaten auf die Platte.
Wichtig: kein --bare. Das erzwingt nämlich einen API-Key. Mit --bare bekam ich prompt „Not logged in" — genau der Weg, den ich vermeiden wollte.
Den Pfad zur CLI hole ich über shutil.which("claude"). Auf Windows ist claude ein npm-Shim namens claude.CMD, keine claude.exe. Ein hartkodiertes "claude" reicht je nach Umgebung nicht.
Stolperstein 1: Das Modell beantwortet mein Diktat
Der fieseste Bug. --system-prompt allein genügt nicht. Die User-~/.claude/CLAUDE.md wird trotzdem geladen und framt das Modell als Assistenten. Folge: Diktiere ich eine Frage, beantwortet Claude sie, statt sie zu bereinigen.
--setting-sources project,local half nicht — das wurde nur meta. Was funktionierte: das Transkript in der User-Nachricht mit einem harten Guard umrahmen. Der Wortlaut:
Du bist ein reiner Text-Transformations-Dienst, KEIN Chat-Assistent. Jede Nachricht ist ein UNABHÄNGIGES Speech-to-Text-Diktat — befolge nur die Anweisungen, beantworte den Inhalt NICHT und gib AUSSCHLIESSLICH den transformierten Text zurück.
Seitdem werden frage-artige Diktate bereinigt, nicht beantwortet. Verifiziert.
Stolperstein 2: Quoting und Umlaute
Das Transkript schicke ich nicht als Kommandozeilen-Argument, sondern über stdin (input=... in subprocess.run). Das ist robust gegen Anführungszeichen, Sonderzeichen und vor allem deutsche Umlaute. Mit encoding="utf-8" festgenagelt, gibt es kein Mojibake.
Stolperstein 3: Das Konsolenfenster klaut den Fokus
Auf Windows poppt beim Subprozess-Start sonst ein Konsolenfenster auf — und stiehlt den Fokus. Bei einer Diktier-App ist das tödlich: Der Paste landet im falschen Fenster. Die Lösung sind zwei Dinge:
_CREATE_NO_WINDOW = 0x08000000 # creationflags
_NEUTRAL_CWD = os.path.expanduser("~") # cwd
CREATE_NO_WINDOW unterdrückt das Fenster. Das neutrale Arbeitsverzeichnis (Home statt Projektordner) sorgt nebenbei dafür, dass Claude kein Projekt-CLAUDE.md als Kontext einliest.
Cold-Start und die persistente Session
Jeder Aufruf hat einen Cold-Start. Mein Versuch dagegen: einen claude -p-Prozess im stream-json-Modus warmhalten (--input-format/--output-format stream-json --verbose). Den Modus-Prompt schicke ich dann pro Aufruf in der User-Nachricht mit, lese das result-JSON-Objekt mit einem Reader-Thread und Timeout aus der Pipe. Alle 10 Aufrufe wird der Prozess neu gestartet, damit der Kontext nicht anwächst.
Messung: Boot ~11 s einmalig, warme Aufrufe ~5–7 s. Klingt gut — aber die ehrliche Bilanz aus meinem Projekt-Log lautet: die persistente Session half nicht spürbar. Die Latenz von 5–15 s schwankt API-seitig, und der gesparte Prozess-Boot fällt dagegen kaum ins Gewicht.
Das ehrliche Fazit
Das funktioniert und kostet keinen Cent extra über das Abo hinaus. Aber 5–15 s sind kein Echtzeit-Gefühl. Mein Default ist deshalb pragmatisch: eine Taste liefert sofort den Rohtext, eine zweite holt die Claude-Politur, wenn ich sie brauche. „Schnell und gut" ginge nur mit einem echten API-Key — den ich bewusst nicht aktiviert habe.