Zachary Davison

Engineering Manager

Berlin, Germany

Unattended debugging with pup and jailed

aidatadog

pup is a Datadog CLI designed for agentic debugging.

I often leave a claude instance running and instruct it to use pup to investigate alerts, incidents, or bugs.

This works well, but annoyingly, claude likes to use python3 to write inline python to process the output from pup.

  pup logs search --query="status:error service:checkout-api" --from="1h" --output=json \                   
    | python3 -c '                                                                                          
  import json, sys                                                                                          
  from collections import Counter                                                                           
                                                                                                            
  logs = json.load(sys.stdin)                                                                               
  by_endpoint = Counter(l["attributes"].get("http.route", "unknown") for l in logs)                         
  print(f"{len(logs)} errors in the last hour")                                                             
  for route, n in by_endpoint.most_common(5):                                                               
      print(f"  {n:4d}  {route}")                                                                           
  '                           

This means I need to continuously approve tool calls for harmless inline python3 scripts, which reduces the usefulness of the claude + pup combo for unattended debugging.

So I wrote jailed, a tool that runs those python3 inline scripts in a sandbox.

You run jailed claude instead of claude, and it will automatically sandbox Turing complete binaries to have no network / filesystem access.

curl -fsSL https://raw.githubusercontent.com/zdavison/jailed/main/install.sh | bash
Removed legacy: /usr/local/bin/unjailed
Installed: /usr/local/bin/jailed
Preserved existing: /Users/z/.config/jailed/srt-settings.json
Preserved existing: /Users/z/.config/jailed/commands
Installed: /Users/z/.claude/hooks/jailed-hook.sh
Merged into: /Users/z/.claude/settings.json

jailed installed.

Claude's calls to these commands will be transparently sandboxed:
  python
  python3
  node
  deno
  bun
  perl
  ruby
  php
  awk
  sed

To enable jailing for another command (e.g. rscript):
  echo 'rscript' >> /Users/z/.config/jailed/commands

To disable jailing for a command, remove or '#'-comment its line in:
  /Users/z/.config/jailed/commands

Quick test:
  echo '<a href=x>' | jailed python3 -c 'import sys; print(sys.stdin.read())'

Jailing is opt-in. To launch Claude in a jailed session:
  jailed claude

Plain 'claude' runs without the hook rewriting anything.

Restart Claude Code (or run /config) to pick up the new hook and permissions.

jailed works by installing a PreToolUse hook for claude which will automatically prefix the configured binaries with jailed.

It walks the process tree, and if any ancestor is jailed, the hook rewrites target tool use calls to be prefixed with jailed (and thus sandboxed with SRT).

These binaries will then be executed in an SRT sandbox.

SRT uses bubblewrap (on Linux) and sandbox-exec (on macOS). It allows you to restrict network & filesystem access as you choose.

jailed by default blocks all network and filesystem, but you can change this by editing:

~/.config/jailed/srt-settings.json

If you’re mid conversation and need to switch off the jail temporarily, you can CTRL+C jailed claude, and re-run it with claude -c to continue the conversation without jailed.