can1357/oh-my-pi · error · anyhow::Error
Invalid pattern: {err}
Error message
Invalid pattern: {err} What it means
Phase seeds in set_todos() must supply 'tasks' as a proper sequence (list/tuple). If 'tasks' is missing, not a Sequence, or is a str/bytes (deliberately excluded because a string is iterable but not a task list), RpcError is raised.
Source
Thrown at crates/pi-ast/src/ops.rs:106
#[must_use]
pub fn is_supported_file(file_path: &Path, explicit_lang: Option<&str>) -> bool {
if explicit_lang.is_some() {
return true;
}
resolve_language(None, file_path).is_ok()
}
pub fn compile_pattern(
pattern: &str,
selector: Option<&str>,
strictness: &MatchStrictness,
lang: SupportLang,
) -> Result<Pattern> {
let selector = selector.map(str::trim).filter(|s| !s.is_empty());
let mut compiled = if let Some(selector) = selector {
Pattern::contextual(pattern, selector, lang)
.map_err(|err| anyhow!("Invalid pattern: {err}"))?
} else {
match Pattern::try_new(pattern, lang) {
Ok(compiled) => compiled,
// A fragment like `"key": $V` parses to multiple root nodes and is
// rejected as `MultipleNode`; auto-wrap it in a single-node context
// before giving up. Any other error, or a failed fallback, keeps the
// original message so genuinely-bad patterns behave as before.
Err(err @ PatternError::MultipleNode(_)) => {
match compile_wrapped_fallback(pattern, strictness, lang) {
Some(compiled) => return Ok(compiled),
None => return Err(anyhow!("Invalid pattern: {err}")),
}
},
Err(err) => return Err(anyhow!("Invalid pattern: {err}")),
}
};
compiled.strictness = strictness.clone();
Ok(compiled)View on GitHub (pinned to 9690622007)
Solutions
- Wrap tasks in a list: [{"title": ...}, ...] — a bare string or dict is rejected.
- Split string-encoded task lists into actual list items before calling set_todos().
- Convert dict-shaped task collections (e.g. {id: task}) into list(task.values()).
- Omit 'tasks' (defaults to ()) only if the library treats a missing key as empty; explicit None also coerces to ().
Example fix
// before
client.set_todos([{"name": "Setup", "tasks": "fix bugs"}])
// after
client.set_todos([{"name": "Setup", "tasks": [{"title": "fix bugs"}]}]) Defensive patterns
Strategy: validation
Validate before calling
tasks = seed.get("tasks")
if tasks is not None and (not isinstance(tasks, (list, tuple)) or isinstance(tasks, (str, bytes))):
raise ValueError(f"phase 'tasks' must be a list, got {type(tasks).__name__}") Type guard
def is_task_sequence(v: object) -> TypeGuard[Sequence[object]]:
return isinstance(v, Sequence) and not isinstance(v, (str, bytes)) Try / catch
try:
client.set_todos(seeds)
except RpcError as exc:
if "must be a sequence" in str(exc):
raise ValueError(f"bad phase tasks: {exc}") from exc
raise Prevention
- Always pass tasks as a list literal, even for a single task.
- Reject strings at your config-loading boundary — a comma-separated tasks string is a common culprit.
- Convert dict-keyed task collections to lists before sending.
When it happens
Trigger: Passing a phase dict without 'tasks', with tasks set to a plain string like "fix bugs", a dict, an int, or bytes instead of a list of task seeds.
Common situations: JSON configs where tasks was accidentally a comma-separated string, single task passed without a wrapping list, or a deserialized payload where tasks became a dict keyed by id.
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- Unsupported language '{value}'. Supported: {}
- Unable to infer language from file extension: {}. Specify `l
- Unsupported todo status: {seed.status}
- Todo items must provide a non-empty 'content' value
- Destination option ${key} must be a string
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/451241ce93350d52.
Report an issue: GitHub.