langchain-ai/deepagents · error · ValueError
`snapshot_signing_key` must be a non-empty str or bytes.
Error message
`snapshot_signing_key` must be a non-empty str or bytes.
What it means
`normalize_signing_key` validates the HMAC key used to sign snapshot files. Keys must be a non-empty str or bytes: strs are UTF-8 encoded, bytes are used verbatim, and empty material is rejected because an empty HMAC key provides no integrity guarantee.
Source
Thrown at libs/partners/quickjs/langchain_quickjs/_snapshot.py:60
SnapshotRecord = tuple[str, bytes]
# Domain-separation prefix folded into every signed message so a snapshot HMAC
# can never be confused with an HMAC computed over some other blob using the
# same key. Bump the version suffix if the signed-message layout ever changes.
_HMAC_DOMAIN = b"langchain-quickjs/snapshot-hmac/v1"
def normalize_signing_key(key: str | bytes) -> bytes:
"""Coerce a user-supplied signing key into raw ``bytes``.
``str`` keys are UTF-8 encoded; ``bytes`` are used verbatim. Empty keys are
rejected because an empty HMAC key provides no integrity guarantee.
"""
material = key.encode("utf-8") if isinstance(key, str) else bytes(key)
if not material:
msg = "`snapshot_signing_key` must be a non-empty str or bytes."
raise ValueError(msg)
return material
def sign_snapshot(key: bytes, payload: bytes, thread_id: str) -> bytes:
"""Return the HMAC-SHA256 tag over a fully materialized snapshot.
The tag is computed over the *completed materialized* snapshot bytes (the
full heap serialization) bound to ``thread_id``, so a valid snapshot for one
thread cannot be replayed into another by a state-store adversary. This is
signed before the payload is delta-encoded (``encode_snapshot``) and flushed
onto the ``bsdiff`` patch chain; verification recomputes the tag over the
materialized bytes the chain replays back to.
"""
return hmac.new(key, _signed_message(payload, thread_id), sha256).digest()
def verify_snapshot(
key: bytes, payload: bytes, thread_id: str, tag: bytes | NoneView on GitHub (pinned to a1af029e6e)
Solutions
- Provide a non-empty secret string, e.g. `snapshot_signing_key=os.environ['SNAPSHOT_KEY']` after confirming it is set.
- Fail fast at startup: assert the key env var is present and non-empty before building the middleware.
- If no signing is intended, omit `snapshot_signing_key` (default None) instead of passing an empty value.
Example fix
// before
middleware = QuickJsMiddleware(snapshot_signing_key=os.environ.get("SNAPSHOT_KEY", ""))
// after
key = os.environ["SNAPSHOT_KEY"]
assert key, "SNAPSHOT_KEY must be set"
middleware = QuickJsMiddleware(snapshot_signing_key=key) Defensive patterns
Strategy: validation
Validate before calling
key = os.environ.get("SNAPSHOT_KEY")
if not key:
raise ValueError("SNAPSHOT_KEY must be a non-empty str or bytes") Type guard
def is_valid_signing_key(key) -> bool:
if isinstance(key, str):
return bool(key.encode("utf-8"))
return isinstance(key, (bytes, bytearray)) and bool(bytes(key)) Try / catch
try:
mw = QuickJsMiddleware(snapshot_signing_key=key)
except ValueError as e:
if "snapshot_signing_key" in str(e):
fix_key_configuration() # load from secret manager / abort startup Prevention
- Load signing keys from a secret manager and fail fast at startup if empty.
- Never pass `os.environ.get(..., "")` defaults for required secrets.
- Omit the parameter entirely when signing is not intended instead of passing an empty value.
When it happens
Trigger: Passing `snapshot_signing_key=""` or `b""` (or a falsy bytes-like object, e.g. `bytearray()`) when constructing the middleware or in direct calls to `normalize_signing_key`.
Common situations: Reading the signing key from an env var or config file that is empty/unset and passing the empty string through without checking; a template or secret manager returning an empty value; typo'd env var name yielding `''`.
Related errors
- `mode` must be one of 'thread', 'turn', or 'call'.
- `max_snapshot_bytes` must be >= 1 or None
- {what} must be absolute: {path}
- Home directory is not absolute: {launch_home}. Set $HOME to
- Invalid {SERVER_ENV_PREFIX}ALLOW_FS_TOOLS value: unknown fil
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/f984048f9a47deda.
Report an issue: GitHub.