Workflow-Werkstatt
Claude & Agenten

Claude Code als Subprozess: KI-Politur ohne API-Key über das eigene Abo

Wie ich `claude -p` headless als Text-Transformations-Dienst einbinde — über das eingeloggte Abo statt über einen API-Key. Mit den genauen Flags, dem Quoting-Trick per stdin und einer ehrlichen Notiz zur Latenz.

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:

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.