NousResearch/hermes-agent · error · TraceRedactionError

Trace upload blocked: secret redaction failed, so the transc

Error message

Trace upload blocked: secret redaction failed, so the transcript may still contain credentials or other sensitive data. Fix the redactor or rerun with --no-redact only after manually reviewing the transcript.

What it means

Raised as TraceRedactionError from agent/trace_upload.py:72 when the shared redactor (agent.redact.redact_sensitive_text with force=True) throws while scrubbing trace text before upload. The upload is deliberately refused (fail-closed) because the transcript may still contain credentials. The underlying exception is logged with exc_info before the re-raise.

Source

Thrown at agent/trace_upload.py:72

def _now_iso() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"


def _redact(text: Any, enabled: bool) -> Any:
    """Redact secrets from a string body when redaction is enabled.

    Non-strings pass through untouched. Uses Hermes' shared redactor with
    ``force=True`` so an upload always scrubs known secret shapes even if
    the user disabled log redaction globally.
    """
    if not enabled or not isinstance(text, str) or not text:
        return text
    try:
        from agent.redact import redact_sensitive_text
        return redact_sensitive_text(text, force=True)
    except Exception as exc:
        logger.warning("Trace upload redaction failed; refusing upload", exc_info=True)
        raise TraceRedactionError(_REDACTION_BLOCKED_MESSAGE) from exc


def _content_to_blocks(content: Any, redact: bool) -> List[Dict[str, Any]]:
    """Normalize a message ``content`` field into Anthropic content blocks."""
    if content is None:
        return []
    if isinstance(content, str):
        return [{"type": "text", "text": _redact(content, redact)}]
    if isinstance(content, list):
        blocks: List[Dict[str, Any]] = []
        for part in content:
            if isinstance(part, dict):
                ptype = part.get("type")
                if ptype == "text":
                    blocks.append({"type": "text", "text": _redact(part.get("text", ""), redact)})
                elif ptype in ("image_url", "image"):
                    # Keep a placeholder; the viewer renders text turns and we
                    # don't want to inline base64 blobs into a trace.

View on GitHub (pinned to c896c09c42)

Solutions

  1. Read the WARNING log line 'Trace upload redaction failed; refusing upload' — the chained exception (exc_info=True) names the exact redactor failure; fix that in agent/redact.py.
  2. Reproduce standalone: from agent.redact import redact_sensitive_text; redact_sensitive_text(<failing text>, force=True) and fix whatever raises.
  3. Add a regression test for the failing input shape so the redactor stays exception-free.
  4. Only after manually reviewing the transcript for secrets, rerun with --no-redact as the message instructs — this is the documented escape hatch, not the default.

Example fix

// before (redactor crashes on some input -> upload refused)
return redact_sensitive_text(text, force=True)

// after (redactor itself must be fixed; keep fail-closed upload behavior)
// e.g. in agent/redact.py, guard the pattern application:
for pattern, repl in _SECRET_PATTERNS:
    try:
        text = pattern.sub(repl, text)
    except re.error:
        logger.exception("bad redaction pattern %s", pattern)
        raise
Defensive patterns

Strategy: try-catch

Validate before calling

from agent.redact import redact_sensitive_text

def redaction_is_healthy(sample: str) -> bool:
    """Run the redactor over representative text before starting a run."""
    try:
        redact_sensitive_text(sample, force=True)
        return True
    except Exception:
        return False

Try / catch

from agent.trace_upload import TraceRedactionError

try:
    upload_trace(messages, redact=True)
except TraceRedactionError:
    # upload was refused; never fall back to uploading unredacted text here.
    log.error("trace upload skipped: redaction failed")
    # surface to the user; only a human may decide on --no-redact after review

Prevention

When it happens

Trigger: Calling the trace-upload path (build_trace_jsonl / upload helpers that call _redact(text, redact=True)) when redact_sensitive_text raises for any reason: a broken regex/pattern in agent/redact.py, an unexpected input type inside the redactor, or a partially broken install where agent.redact imports but fails at runtime.

Common situations: A change to the redaction patterns introduces an exception on certain payloads; a new secret shape (e.g. unusual token format) hits an untested branch; environment differences (locale, missing dependency used by the redactor) make the redactor crash only in CI or on another machine.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/e2897970ca8f5e67. Report an issue: GitHub.