Skip to content

devlog/api-plumbing/terminal-request-logs-that-keep-llm-prompts-private-96.md

Terminal Request Logs That Keep LLM Prompts Private

Keep prompts out of your terminal request logs

A failed LLM request usually sends me looking for three things: whether the server answered, how long it took, and whether retrying makes sense. The conversation rarely belongs in that investigation. Logging the entire request because it is convenient turns your debug directory into another database of user data.

Here is a small Python probe that logs an explicit set of metadata fields. It never serializes the request, response, exception message, or headers. The example uses Fluxo, where one key and the base URL https://api.fluxorelay.com cover Claude, GPT, Gemini, DeepSeek, Kimi, GLM, Qwen, Grok, and more.

Create a private Python workspace

Run these commands in Bash. The restrictive umask makes newly created files accessible only to your user. Keep this directory outside your application repository and any synced folder.

umask 077; mkdir -p "$HOME/.local/state/fluxo-probe"; cd "$HOME/.local/state/fluxo-probe"

python3 -m venv .venv

. .venv/bin/activate — python -m pip install openai

Read the key without displaying it or saving it in shell history. Disable shell tracing first; tracing can expose later variable expansions.

set +x; read -r -s -p 'Fluxo API key: ' FLUXO_API_KEY; printf '\n'; export FLUXO_API_KEY

Choose a model such as GPT-6 Astra, then copy its exact API identifier from Fluxo into this prompt. Display names and API identifiers are not necessarily interchangeable.

read -r -p 'Exact model ID: ' FLUXO_MODEL; export FLUXO_MODEL

Log an allowlist around one SDK request

Paste the following as one terminal command. The literal backslash-n sequences inside the Python string become newlines when exec runs. The SDK uses the OpenAI-compatible endpoint beneath Fluxo’s base URL.

python -c 'import json, os, time, uuid; from openai import OpenAI, APIStatusError, APIConnectionError; client = OpenAI(api_key=os.environ["FLUXO_API_KEY"], base_url="https://api.fluxorelay.com/v1", max_retries=0, timeout=30.0); event = {"request_id": str(uuid.uuid4()), "operation": "chat.completions", "outcome": "error"}; started = time.monotonic(); exec("try:\n response = client.chat.completions.create(model=os.environ[\"FLUXO_MODEL\"], messages=[{\"role\": \"user\", \"content\": \"Reply with OK.\"}])\n event.update(outcome=\"success\", status=200)\n if response.usage is not None:\n event.update(input_tokens=response.usage.prompt_tokens, output_tokens=response.usage.completion_tokens)\nexcept APIStatusError as error:\n event.update(error_kind=\"http\", status=error.status_code)\nexcept APIConnectionError:\n event.update(error_kind=\"transport\")\nexcept Exception:\n event.update(error_kind=\"client\")\nfinally:\n event[\"elapsed_ms\"] = round((time.monotonic() - started) * 1000)\n with open(\"requests.jsonl\", \"a\", encoding=\"utf-8\") as output:\n output.write(json.dumps(event) + \"\\n\")")'

Each run appends one JSON object. The request ID is generated locally, so it carries no user identity. Retries are disabled to make this probe’s timing easier to interpret. The 200 value records a successful SDK call; it is not a captured response header.

The exception handlers deliberately discard error messages. Upstream errors can echo submitted content. The same problem applies to debug middleware, HTTP traces, and printing SDK objects, so leave those disabled while testing.

Check the actual file before adding real traffic

Inspect the metadata, then verify that every record contains only approved keys: — cat requests.jsonl

python -c 'import json; allowed = {"request_id", "operation", "outcome", "status", "input_tokens", "output_tokens", "error_kind", "elapsed_ms"}; rows = [json.loads(line) for line in open("requests.jsonl", encoding="utf-8")]; assert rows; assert all(set(row) <= allowed for row in rows); print("Metadata allowlist passed")'

This checks the schema, not every possible leak elsewhere in your application. Before connecting real traffic, also inspect your process supervisor and observability agent configuration.

  • Keep prompts, completions, authorization headers, and exception messages outside application logs.
  • Avoid user identifiers and prompt hashes, which can still reveal relationships.
  • Restrict access and set retention limits wherever these records are collected.

Keep token accounting separate from conversation content

Fluxo bills per real token, including cache reads and writes, with no monthly fee. These aggregate token fields help explain traffic; use billing records for cost reconciliation because cache pricing needs additional detail.

Fluxo also works with Claude Code, Codex, Gemini CLI, and any OpenAI SDK, but this wrapper only controls its own log file. Review each tool’s local history separately. Referral shares accrue when invited users pay; keep those records separate from request diagnostics.

If you want to run this against your own traffic, create a Fluxo account and start with the harmless probe above. If you need help, leave a note on the contact page after signing in.

Next in the buffer

Sign up in a minute and get credit to try every model mentioned here. Sign up →