HKUDS/Vibe-Trading · error · PlaybookError
playbook {self.slug!r} has no variable {key!r}; declared: {s
Error message
playbook {self.slug!r} has no variable {key!r}; declared: {sorted(values)} What it means
Raised by Playbook.render (agent/src/scheduled_research/playbooks.py:128) as PlaybookError when the variables dict passed to render contains a key that the playbook's frontmatter did not declare. Render only fills declared variables, so supplying an extra key is treated as a caller bug and rejected with the declared key list in the message.
Source
Thrown at agent/src/scheduled_research/playbooks.py:128
def render(self, variables: Optional[Mapping[str, Any]] = None) -> str:
"""Return the instruction body with placeholders substituted.
Args:
variables: Overrides keyed by declared variable name. A key that
is not declared by this playbook is an error rather than a
silent no-op. A blank value falls back to the declared default.
Returns:
The prompt text to hand to the scheduler.
Raises:
PlaybookError: If an undeclared variable is supplied or a value
exceeds the length cap.
"""
values = dict(self.variables)
for key, raw in (variables or {}).items():
if key not in values:
raise PlaybookError(
f"playbook {self.slug!r} has no variable {key!r}; declared: {sorted(values)}"
)
text = str(raw).strip()
if len(text) > _MAX_VARIABLE_CHARS:
raise PlaybookError(
f"playbook variable {key!r} is {len(text)} chars; the cap is {_MAX_VARIABLE_CHARS}"
)
if text:
values[key] = text
# Every placeholder in the body was checked against ``variables`` at
# load time, so this substitution cannot leave one behind.
return _PLACEHOLDER_RE.sub(lambda m: values[m.group(1)], self.body)
def to_job(
self,
*,
job_id: Optional[str] = None,
schedule: Optional[str] = None,View on GitHub (pinned to 80ffdda44c)
Solutions
- Check the message's 'declared:' list and remove/rename the extra key
- Keep per-playbook variables dicts rather than one shared mapping
- Re-read the playbook frontmatter after edits and update callers
- Separate config fields from template variables at the call site
Example fix
# before
job = playbook.to_job(schedule="@daily", variables={"topic": "AI", "extra": 1})
# after
job = playbook.to_job(schedule="@daily", variables={"topic": "AI"}) Defensive patterns
Strategy: validation
Validate before calling
def filter_declared(playbook, variables):
declared = set(playbook.variables)
return {k: v for k, v in (variables or {}).items() if k in declared} Type guard
def only_declared_variables(playbook, variables):
"""Return variables restricted to the playbook's declared keys."""
declared = set(playbook.variables)
extras = set(variables or {}) - declared
if extras:
raise ValueError(f"undeclared variables: {sorted(extras)}; declared: {sorted(declared)}")
return dict(variables) Try / catch
from agent.src.scheduled_research.playbooks import PlaybookError
try:
job = playbook.to_job(schedule="@daily", variables=vars)
except PlaybookError as exc:
if "has no variable" in str(exc):
vars = {k: v for k, v in vars.items() if k in playbook.variables}
job = playbook.to_job(schedule="@daily", variables=vars)
else:
raise Prevention
- Derive variable dicts from playbook.variables, not a shared global config
- Regenerate/review callers after editing playbook frontmatter
- Keep template variables separate from job config keys
When it happens
Trigger: Calling to_job or build_job_from_draft with variables={'topic': 'x', 'depth': 2} when the playbook only declares 'topic'. The message lists sorted declared keys, e.g. declared: ['topic'].
Common situations: Renaming a playbook variable in its markdown file but not in callers; sharing one variables dict across multiple playbooks with different declarations; passing config keys (like timezone or model) inside the variables mapping.
Related errors
- playbook variable {key!r} is {len(text)} chars; the cap is {
- source.playbook_slug is required
- source.variables must be an object
- timezone {tz!r} is not a recognized IANA timezone key
- unknown delivery status {raw_status!r}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/9f9defce29968773.
Report an issue: GitHub.