Skip to content

FAQ & Troubleshooting

General

Do I have to use SecurityContext? Can I just use the tools as-is?

Yes — pass nothing (ctx=None, or just don't pass ctx at all) and every tool behaves like a normal, unrestricted LangChain tool. Nothing in this package is forced on you; the security layer only activates when you explicitly configure it. See Quickstart.

Why does make_process_tool() print a warning?

Two separate warnings can appear:

  • Building it with no SecurityContext at all warns because that configuration means "run any command, no limits whatsoever" — worth an explicit heads-up even though it's allowed.
  • Building it with shell=True (the default) warns that command chaining via shell metacharacters (;, |, &&) isn't individually validated against allowed_commands/denied_commands — only the outer command string is checked.

Neither warning stops the tool from working; they're informational. See Process Tool for the full tradeoff and recommended mitigation (shell=False + allowlist mode).

What happens if I don't set paths?

An empty paths list means "no restriction on location" — the context still enforces permissions, require_approval_for, and (for process/rsync) command rules, but any path anywhere on the filesystem is otherwise reachable. If you want a true sandbox, set paths explicitly with at least one PathEntry (typically slug="root" for a single directory). See Security & Sandboxing.

Approval

CLIApproval isn't prompting me / raises EOFError

CLIApproval uses a blocking input() call, which requires an interactive terminal with a real stdin. If you're running non-interactively (a script piped from a file, a CI job, a background process), there's nothing to prompt — by default it auto-denies on EOFError (auto_deny_on_eof=True). For non-interactive environments, use InterruptApproval (LangGraph), CallbackApproval (wrap your own logic), or AutoApprove()/AutoDeny() if you specifically want a non-interactive fixed decision.

require_approval_for is set but nothing seems to be blocking — why did the action still go through?

Double check the flag actually overlaps: require_approval_for only gates the specific Permission flags you list. require_approval_for=Permission. DELETE won't gate WRITE actions. Also confirm approval isn't None — if it is, the action should be denied, not silently allowed (fail-safe default); if you're instead seeing it silently succeed, check whether the permission you expected to be gated is actually the one the operation you called maps to (see each tool's operation-to-permission table).

ImportError: InterruptApproval requires langgraph

Install the extra: pip install -e ".[langgraph]". See Installation.

Search & Rsync

Do I need to install ripgrep or rsync?

No — both are optional. file_search_tool falls back to grep, then a pure-Python implementation; rsync_tool falls back to a Python copy-based sync. Installing the binaries just makes those tools faster; behavior is otherwise equivalent. See Installation.

My search results seem incomplete / capped

max_results defaults to 100 and is hard-capped at 1000 regardless of what you request — this is intentional, since results are meant to be read by an LLM in one response. Narrow your query/glob/path instead of trying to raise the cap further, or run multiple, more targeted searches. See Search Tool.

Commands (process_tool / rsync_tool)

My allowlist glob isn't matching what I expect

Glob patterns (anything containing * ? [ ]) are matched against the whole command string, case-insensitively — not as a substring search. "git *" matches "git status" but not "my git status" (leading text before "git" means it doesn't match the whole-string shape). If you need "contains this substring/construct anywhere," use an explicit re: pattern instead, e.g. r"re:\bgit\b". See Security & Sandboxing for the reasoning behind this being explicit rather than guessed.

A command I expect to be blocked (or allowed) isn't behaving as expected

Remember the check order: denied_commands is checked first and always wins, even over an explicit allowed_commands entry. If allowed_commands is non-empty, it's allowlist mode — anything not matching something in it is denied by default, regardless of whether denied_commands says anything about it at all.

Why is bash -c '...' blocked even though I didn't ask for that?

It's in the default denied_commands list. The pattern matcher can only ever see the outer command string — it has no way to safely parse what's inside an opaque sub-shell string like bash -c 'rm -rf /', so none of the other denylist rules could catch a dangerous command hidden that way. The wrapper itself is blocked as the only way to guarantee the rest of the denylist can't be trivially bypassed. See Security & Sandboxing.

Can I fully trust the command denylist to keep an agent safe?

Treat it as best-effort, not a hard guarantee — it's string matching, not a real shell parser. For anything with real filesystem access, prefer allowed_commands (allowlist, default-deny) over relying on denied_commands alone, and pair it with process_tool's shell=False mode, which removes the shell-interpretation layer entirely rather than trying to filter around it. See Process Tool.

Errors

What's the difference between a "Denied: ..." and an "Error: ..." response?

"Denied: ..." means the security layer (a MeroToolsError subclass) stopped the action — a path/permission/command/approval check failed. "Error: ..." means the action was allowed but failed for an ordinary reason (missing file, missing required argument, unknown operation). See Error Handling.

A tool call raised a real Python exception instead of returning a string — is that a bug?

Only MeroToolsError and its subclasses are caught and converted to strings. Any other exception (a genuine bug, an unexpected OS error, etc.) propagates normally — this is intentional, so real bugs stay visible rather than being silently reported to the agent as a permission issue. If you hit an unexpected raised exception that you believe should have been handled cleanly, that's worth reporting as a bug in the specific tool.

Still stuck?

  • Re-check Core Concepts for how guard() ties everything together — most "unexpected behavior" questions trace back to a permission/path/approval interaction covered there.
  • Check the specific tool's own doc page for operation-level detail: File, Directory, Search, Process, Rsync.
  • The test suite (tests/) is a good source of concrete, working examples for every feature described in these docs.