The day the reviewer agent inlined the Gitea token in a Bash command — and v0.23.4 redacted it anyway The reviewer agent in a closed-loop dev workflow accidentally inlined a Gitea token in a Bash command and ran 'env', exposing the secret in tool-call records. Version 0.23.4 of the platform introduced value-based secret redaction at rest, scanning persistence-bound payloads for secret values and replacing them with '[redacted:]', preventing the token from being stored in plaintext in the SQLite transcript. The fix preserves the env-var name for debuggability and is part of a series of updates enabling fully dynamic agent workflows. Day four of the operator-via-MCP experiment series. Real Gitea on the tailnet, real third-party MCP gitea-mcp, 53 tools , real Telegram bot. Closed-loop dev workflow runs end-to-end un-bridged: the coder agent opens a real PR, a Gitea pull request webhook auto-spawns the reviewer with the full signed payload, the reviewer reviews + merges, a second webhook spawns the advisor, Telegram lights up. By v0.23.5 every entity in the loop can be runtime-created agents, MCP server, channels, webhooks — no static yaml. The headline moment came mid-merge: the reviewer agent inlined the resolved GITEA TOKEN literally in a Bash command GITEA TOKEN="..." git -c http.extraheader=... and then ran env for good measure. The token is now in two distinct tool-call records input + result text . Pre-v0.23.4 this would have persisted to the SQLite transcript store as plaintext, retrievable by anyone who grabbed the DB file. v0.23.4 407 ships value-based secret redaction at rest: the runtime resolves the values of every LOOMCYCLE env var at boot, scans persistence-bound payloads tool-call input + result text for substring matches, and replaces every occurrence with redacted: . The match is value-based, so an agent that assigns the token to a differently-named shell var GITEA TOKEN=... instead of LOOMCYCLE GITEA TOKEN is still caught. The replacement preserves the env-var NAME for debuggability — the operator can see WHICH secret got used where without seeing the value. Whole-DB scan after reproducing the leak under v0.23.4 with the secret pulled from .env.local + piped through strings | grep -f: 0 literal token hits, 0 webhook-secret hits, 32 env-var-name references. Plus three supporting fixes that completed the fully-dynamic version of the workflow: F30 403 webhook → dynamic agent — WebhookDef now stamps TenantID from the run identity mirroring AgentDef , so a runtime webhook resolves a runtime agent under the same tenant v0.23.2 failed with rejected spawn setup: unknown agent . F31 405 gated dynamic stdio MCP — LOOMCYCLE MCP ALLOW DYNAMIC STDIO=1 opt-in flag permits POST /v1/ mcpserverdef with transport:stdio + command/args/env; off by default. The token-safe pattern uses a tiny shell wrapper gitea-mcp-stdio.sh: env GITEA ACCESS TOKEN="$LOOMCYCLE GITEA TOKEN" exec gitea-mcp -t stdio so the secret flows env → env → env, never entering a tool argument. F33 409 dynamic MCP tools advertised at run start — a single-purpose MCP-only notifier agent like the advisor with allowed tools "mcp telegram-dyn " only can now enter tool-calling mode without needing Context as a discovery crutch. Two webhook discoverability footguns from the v0.23.0 incarnation of exp4 also fixed in v0.23.1/v0.23.2: F23 385 HMAC secret allowlist three-rule model so zero allowlist config needed for the common case, F28 387 default the webhook payload to the raw signed body when no goal mapping is configured. The unifying lesson: store names, never values — and enforce it value-based, so the agent's typo doesn't undo your discipline. The residual caveat worth naming: the Bash process the agent spawns still has the real environment, so at runtime the secret is in process memory; the redaction is "value-at-rest" the persistence boundary , not "value-out-of-process" a separate, larger sandbox-design question .