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

  1. Wrap tasks in a list: [{"title": ...}, ...] — a bare string or dict is rejected.
  2. Split string-encoded task lists into actual list items before calling set_todos().
  3. Convert dict-shaped task collections (e.g. {id: task}) into list(task.values()).
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/451241ce93350d52. Report an issue: GitHub.