langchain-ai/deepagents · error · TypeError
Invalid skill source: expected str or (str, str) tuple, got
Error message
Invalid skill source: expected str or (str, str) tuple, got {source!r} What it means
Skill sources may be given as a plain path string or a (path, label) two-element string tuple. `_validate_tuple_source` rejects anything else — wrong tuple length, non-string elements, or other types — with a TypeError describing the received value. This keeps path/label bookkeeping (self.sources / self.source_labels) index-aligned.
Source
Thrown at libs/deepagents/deepagents/middleware/skills.py:175
name). The label is rendered as `**{label} Skills**` in the system
prompt; do not include the trailing "Skills" yourself.
"""
def _validate_tuple_source(source: tuple[object, ...]) -> None:
"""Raise `TypeError` if a tuple source is not a `(str, str)` pair.
Catches the near-miss shapes at construction time so the traceback
points at the caller rather than at a later `IndexError` inside the
middleware or a silently-coerced non-string path downstream.
"""
if (
len(source) != 2 # noqa: PLR2004 # SkillSource tuple is exactly (path, label)
or not isinstance(source[0], str)
or not isinstance(source[1], str)
):
msg = f"Invalid skill source: expected str or (str, str) tuple, got {source!r}"
raise TypeError(msg)
def _source_path(source: SkillSource) -> str:
"""Return just the path component of a source."""
if isinstance(source, str):
return source
_validate_tuple_source(source)
return source[0]
def _truncate_skill_load_warning(error: str) -> str:
"""Cap a skill loading warning before placing it in the model prompt."""
if len(error) <= MAX_SKILL_LOAD_WARNING_LENGTH:
return error
length = MAX_SKILL_LOAD_WARNING_LENGTH - len(_SKILL_LOAD_WARNING_TRUNCATION_SUFFIX)
return f"{error[:length]}{_SKILL_LOAD_WARNING_TRUNCATION_SUFFIX}"
View on GitHub (pinned to a1af029e6e)
Solutions
- Use str(pathlib_path) instead of a Path object.
- Ensure each tuple is exactly (str_path, str_label), e.g. ("/skills", "team-skills").
- Pass a plain string if no custom label is needed.
- Validate config-derived sources with a quick check before constructing the middleware.
Example fix
// before
sources=[Path("./skills")]
// after
sources=[str(Path("./skills")), ] # or ("./skills", "local") Defensive patterns
Strategy: type-guard
Validate before calling
def norm_source(s):
if isinstance(s, str):
return s
if isinstance(s, tuple) and len(s) == 2 and all(isinstance(x, str) for x in s):
return s
raise TypeError(f"bad skill source: {s!r}")
sources = [norm_source(s) for s in cfg_sources] Type guard
def is_skill_source(v: object) -> TypeGuard[str | tuple[str, str]]:
if isinstance(v, str):
return True
return isinstance(v, tuple) and len(v) == 2 and all(isinstance(x, str) for x in v) Try / catch
try:
mw = SkillsMiddleware(backend=b, sources=sources)
except TypeError as e:
if "Invalid skill source" in str(e):
mw = SkillsMiddleware(backend=b, sources=[str(s) for s in sources])
else:
raise Prevention
- Convert pathlib.Path to str before adding to sources.
- Use tuples of exactly (path, label), both strings.
- Validate config-decoded sources (JSON yields lists, not tuples).
- Centralize source normalization in one helper.
When it happens
Trigger: SkillsMiddleware(..., sources=[("/skills",)]) (length-1 tuple); [("/skills", 42)]; passing a Path object or a 3-tuple.
Common situations: Using pathlib.Path instead of str; packing an extra metadata field into the tuple; YAML/JSON config decoding tuples as lists of mixed types.
Related errors
- system_prompt must be str or None, got {type(system_prompt).
- interpreter_ptc must be False, 'safe', 'all', or a list of t
- max_retries must be an int, got {type(max_retries).__name__}
- stream_output_is_visible must be a bool, got {type(stream_ou
- context.{key} must be a string or null, got {type(value).__n
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/8384a5466f4866ba.
Report an issue: GitHub.