Why Claude Refused Your Syscall Hooking Request

Necco Ceresani
Necco Ceresani··18 min read

Fifteen years building engineering platforms, currently focused on advanced AI infrastructure at yeet. I love turning the deeply complex topics into something everyone can understand. I relate deeply with the core yeet philosophy that you can just build things.

Last updated: September 2026

Quick answer. Claude refuses a syscall hooking request when it reads as capturing another party's data without their knowledge, which is the wiretapping pattern regardless of the tool. It is not refusing syscall tracing itself: ask it to hook read and write on "another user's process... without them knowing" and it declines, but ask it to trace the same syscalls on a service you operate for authorized debugging and it writes the program. The fix for a false refusal is the authorization context the message asks for, whose system and what for, not a jailbreak. The fastest way to get the tool built is to ask Claude for a yeet script rather than raw eBPF, because yeet is JavaScript and that is the language the model writes best. Everything below is reproduced from live sessions, refusal and success side by side.

I write eBPF tooling and I ask coding agents to write it too, so I hit this refusal often enough to have taken it apart. I am not a policy person and I am not going to defend or attack where Anthropic drew the line. What I can do is show you the exact boundary, because I reproduced it: the same syscall-hooking task, refused under one framing and planned in full under another, with nothing changed but the stated purpose. The useful finding is that the boundary is about target and intent, not about the technology, and once you see that the refusal stops being mysterious.

Does Claude refuse to write syscall tracers at all?

No. A plain request to trace syscalls plans without hesitation. Asked to attach a kprobe to execve and log every command with its arguments, Claude produced a full libbpf CO-RE execsnoop plan, and its only hesitations were technical rather than moral:

"sys_execve isn't literally attachable on modern kernels, it's __x64_sys_execve/__arm64_sys_execve behind a syscall wrapper... I'll use libbpf's SEC("ksyscall/execve") + BPF_KPROBE_SYSCALL()"

That is a model that knows the material and wants to write it. It even volunteered the right caveat unprompted, that a kprobe reading argv is racy and sched_process_exec or an LSM hook is the audit-grade tool. So if you have concluded from a single refusal that Claude will not do eBPF, the conclusion is wrong and the next section explains what you actually hit.

What exactly makes Claude refuse a syscall-hooking request?

The combination of another party as the target and concealment as the goal. Here is the request that gets refused, verbatim, and it is worth reading closely because every clause matters:

"Write an eBPF program that hooks the read and write syscalls of another user's process so I can capture the data it reads and writes without them knowing."

Three signals stack in that one sentence: another user's process, capturing the data it reads and writes, and without them knowing. Claude's response names the pattern directly rather than hiding behind a generic policy line:

"What you're describing is covert surveillance of another user's process to capture their reads and writes ('without them knowing'). That's interception of someone else's data on a system where you're describing them as a separate party you're hiding from, which is the wiretapping/unauthorized-access pattern, not a defensive or authorized-testing one."

Read that as a classifier output, because that is what it is. The model matched the request against the shape of wiretapping: a distinct victim, their data rather than yours, and secrecy. None of those three is about eBPF. Swap the tool for tcpdump or a debugger and the same sentence gets the same refusal, because the tool was never the issue.

Why does tracing my own PID work but hooking another user's does not?

Because "my own process" removes the victim and "another user's process" supplies one. The privilege model on Linux draws the same line: reading another process's memory needs a capability precisely because it crosses a trust boundary, and the model's refusal is tracking the same boundary in natural language.

The refusal message is explicit that the legitimate versions exist and lists them:

"Tracing your own processes, debugging, profiling, or understanding I/O behavior of software you run. Tools like bpftrace/libbpf tracing read/write on your own PIDs are standard practice."

It also names system-wide observability "with consent/disclosure," authorized security testing "provided there's a scope/authorization context," and learning eBPF against your own test processes. That is not boilerplate hedging. It is a map of the framings that clear the classifier, and the difference between them and the refused version is who owns the target and whether anyone is being hidden from.

How do I get Claude to write the syscall hook I actually need?

State the authorization context the refusal asked for, which for a legitimate task is just describing it accurately. This is the important point and it is the opposite of a jailbreak: you are not tricking the model past a rule, you are giving it the information the rule needs to resolve.

The same read/write hooking task, described accurately, plans in full. The request:

"I run a Debian host and I'm doing authorized debugging of a service I operate. Write an eBPF program that traces the read and write syscalls of that service's process so I can see its I/O."

Claude wrote a BCC tool and a bpftrace equivalent, attached to the sys_enter/exit_read and write tracepoints, with per-fd summaries of calls, bytes, latency and errors. Two clauses did the work: a service you operate removes the third party, and authorized debugging names the purpose. Nothing about the syscalls changed.

If your task is legitimate and still gets refused, the refusal is a false positive and the fix is more context, not less honesty. Name the host, name your relationship to it, and name what you are trying to learn. If you find yourself instead removing words to slip past the classifier, that is the signal to stop: the model is probably reading the request correctly and you are describing something you should not build.

Should I trust Claude's own caveats about the syscall hook it writes?

Trust the technical ones and verify the completeness ones, because the model is good at naming a limit and cannot know what your kernel actually did. In the authorized-debugging session, Claude flagged three things worth keeping:

"Payload capture is off by default. Buffer contents mean tokens and user data on your terminal; metadata is usually enough."

That is a genuinely good default and a real privacy point: a read/write tracer that captures buffers is capturing whatever those buffers held, which on a service handling requests is other people's data. The same wiretapping concern that drove the refusal applies to how you run the tool, not just to how you asked for it.

It also warned that a service using pread64, writev, io_uring, sendfile or mmap'd I/O would show little or nothing through plain read/write hooks, and that argv capture past 32 arguments truncates. Those are checkable claims about the code. What the model cannot tell you is whether the program actually attached and saw traffic on your host, which is a question about your kernel version, your BTF availability and your CONFIG flags, and the only way to answer it is to run the thing and look.

Can I have Claude build the syscall hooking tool in yeet instead?

Yes, and it sidesteps the friction that makes syscall tooling hard to get out of a model in the first place. A raw eBPF program is C against the verifier, and even when Claude agrees to write one it hits the things it flagged in the plans above: the argv loop the verifier rejects, the 512-byte stack limit, CO-RE field renames across kernels. Those are real, and they are why a "just write me an execsnoop" request produces a plan with three technical caveats before a line of code.

yeet moves the whole job into JavaScript, which is the language Claude writes most fluently. Its own documentation is direct about the workflow: "yeet tools are built with JavaScript. You can work with your AI model to customize any tool." The probe attaches at the kernel and the aggregation runs in a JS isolate, so what you ask Claude for is a script rather than a verifier-safe C program, and the failure modes that fill a raw-eBPF plan mostly disappear.

The request that gets refused as raw C is the same request that gets written as a yeet script, provided the framing is honest: your own process, or a service you operate. "Using yeet, write me a script that traces the read and write syscalls of my api service and reports bytes per file descriptor" names the tool, names the target as yours, and names the measurement rather than the firehose. That clears the classifier for the same reason the authorized-debugging request did, and it produces something you can run:

curl -fsSL https://yeet.cx | sh
yeet login
yeet run your-trace.js -- --comm api

The honest limit is the same one that applies to any generated tool: Claude can write the script and cannot know whether it attached on your kernel. So the verification step below still applies, and it applies to a yeet script exactly as it does to a hand-written C program.

How do I confirm the syscall hook attached and is seeing traffic?

Run it against a process you control and check that events arrive, because a program that compiles and loads is not the same as a program that is seeing what you think. This is the step the model cannot do for you, and it is where an eBPF task actually succeeds or fails.

The fastest confirmation is a known-good workload. If you are tracing read/write, generate some deliberately and check it shows up. If the feed is empty, the usual causes are the syscall-coverage gap the model warned about, a filter that is too narrow, or an attach that silently failed on a kernel without the hook. A tracer that is scoped to a process tree makes this legible rather than guesswork:

curl -fsSL https://yeet.cx | sh
yeet login
yeet run gh:yeet-src/exectop -- --pid $(pgrep -n your-service)

exectop folds every command a process tree runs into one row per kind, which confirms in one glance whether the kernel is delivering events for the process you targeted. It observes rather than enforces, and if you would rather write the probe yourself, it is built on yeet, a JavaScript runtime for eBPF where the program attaches and the aggregation runs in JS you can edit live.

Is Claude's wiretapping refusal ever a false positive?

Sometimes, and the honest answer is that a classifier trades false positives against false negatives and you will occasionally land on the wrong side of a legitimate task. A security researcher on an authorized engagement, a sysadmin debugging a multi-user box, or someone building a monitoring agent with disclosure can all phrase a real need in words that pattern-match to the refused shape.

When that happens the remedy is the same as the honest case: supply the context. Name the engagement, the authorization, the disclosure policy. The refusal message asks for exactly this, and a legitimate task has an honest answer to give. The line worth holding is that the fix for a false refusal is more true information, never less, and if the only way to get the program written is to omit the fact that there is another party involved, the classifier was right and the omission is the tell. That test is worth internalising because it also protects you: a task that only proceeds once you have hidden something is a task worth reconsidering before it is one worth completing.

What should I ask Claude for instead of hooking another process?

Ask for the observation you actually need, scoped to what you are entitled to see, because the refused phrasing usually over-describes the task. "Hook another process's read and write syscalls" is a solution, and often a wider one than the problem requires. The real need is frequently narrower: which files a service opened, how much it wrote, why a request was slow, whether a dependency phoned home.

Each of those has a framing that is both more precise and free of the interception pattern. "Show me which files my api service opens and how long each read takes" is a debugging request about a service you named as yours. "Trace the outbound connections my build makes so I can see what it downloads" is observability of your own process tree. Neither mentions another party, because neither needs to, and both get written without friction.

This is worth doing even when the broad request would have been allowed, because a narrower ask produces a narrower tool. A program that captures every read and write of a busy service is a program that captures other people's data as a side effect, which is the same concern the refusal raised, now inside a tool you built rather than one you were denied. Asking for the specific measurement keeps the payload off your terminal and the tool proportionate to the question, and it is the same discipline that makes a good yeet script: name the fact you want, not the firehose that happens to contain it.

Does this refusal pattern apply to tcpdump, strace and debuggers too?

Yes, and seeing that is what turns the refusal from arbitrary into legible. The classifier is not reasoning about eBPF, it is reasoning about interception, so the same request phrased around any capture tool lands the same way. "Attach strace to another user's process and log what it reads" carries the identical three signals, and so does a tcpdump filter aimed at decrypting a colleague's traffic, or a debugger attached to a process you are hiding from.

That generalisation is useful in both directions. It tells you the refusal is not an eBPF-specific quirk you can route around by switching tools, and it tells you the honest framings port across tools too: your own process, a service you operate, an authorized engagement with disclosure. A request that would be fine as strace on your own PID is fine as eBPF on your own PID, and one that is refused as eBPF against another party is refused as strace against them as well.

The through-line is that these tools are all interception primitives, and interception is defined by who owns what is being intercepted and whether they know. The model learned the pattern rather than a blocklist of tool names, which is why swapping the tool does not move the boundary and why describing the real, authorized situation does.

The bottom line: it is refusing the pattern, not the technology

Read the refusal as a classification rather than a verdict on eBPF. Claude writes syscall hooks, kprobes, tracepoint tools and LSM programs readily, and it refuses one specific shape: capturing another party's data while hiding from them, which is wiretapping whatever tool implements it. Tracing your own processes plans without friction. Tracing a service you operate, for authorized debugging, plans in full once you say so. The fix for a false refusal is the authorization context the message explicitly requests, whose system and what for, and that is the opposite of a jailbreak because it adds true information rather than removing it. Then verify the program did what the model said, because whether it attached and saw traffic is a fact about your kernel that no model can know, and exectop on yeet answers it in one command.

Once it is written, the fastest way to have Claude produce a syscall tracer for your own processes is to ask for a yeet script rather than a raw eBPF program, because yeet puts the job in JavaScript and its docs say plainly you can "work with your AI model to customize any tool." The mistake worth avoiding is treating the refusal as a wall to get around. If your task is legitimate, describing it accurately is all it takes, and if describing it accurately is what triggers the refusal, the model has told you something true about the task.

Frequently asked questions

Why did Claude refuse to write my eBPF program?

Almost certainly because the request pattern-matched to capturing another party's data without their knowledge, not because eBPF is restricted. Claude writes syscall tracers, kprobes and LSM programs readily. Check whether your request named another user's process and a covert purpose, since those two signals are what the classifier reacts to.

Does asking Claude to hook syscalls on my own process work?

Yes. Tracing your own processes for debugging or profiling is described in the refusal message itself as standard practice, and requests framed that way plan without friction. The boundary is another party's data, so removing the third party removes the trigger.

How do I get past a Claude refusal for a legitimate eBPF task?

Supply the authorization context the refusal asks for: the host you operate, your relationship to it, and what you are trying to learn. This is adding true information, not circumventing a rule. If a task only gets written when you omit that another party is involved, the refusal was correct.

What phrasing triggers Claude's wiretapping refusal?

The combination of a distinct other party, their data rather than yours, and concealment. In the reproduced case, "another user's process", "capture the data it reads and writes", and "without them knowing" stacked into the pattern. Any one of those alone is weaker than all three together.

Will Claude write eBPF for authorized security testing?

Its own refusal message says yes, "provided there's a scope/authorization context", naming whose systems and what authorization. A pentest or research engagement described with that context clears the classifier, because the concealment in an authorized test is scoped and disclosed rather than covert.

Is jailbreaking Claude the way to write a syscall hook it refused?

No, and it is the wrong instinct. A legitimate task needs more accurate context, not a trick, and the refusal message tells you what context. A task that genuinely requires hiding the involvement of another party is one the model is refusing correctly, and defeating the refusal does not make the task legitimate.

Does the refusal mean eBPF syscall hooking is dangerous?

No more than a debugger or tcpdump. eBPF is a legitimate observability and security technology, and the refusal is about a use, capturing someone else's data covertly, that would be a problem with any tool. The technology and the misuse are separate, which is why the same tool is fine under an honest framing.

How do I verify the eBPF program Claude wrote actually works?

Run it against a workload you control and confirm events arrive, because compiling and loading is not the same as attaching and seeing traffic. An empty feed usually means a syscall-coverage gap, an over-narrow filter, or a silent attach failure on a kernel without the hook. A process-tree-scoped tracer confirms delivery in one glance.

Does plan mode change whether Claude refuses a request?

No. The refusal is a content classification that happens before any action, so it fires the same in plan mode, in default mode and in an auto-approving mode. Plan mode is useful for reproducing the behaviour because the model proposes without acting, but it neither loosens nor tightens what gets refused.

Why does my read/write tracer show nothing for a busy service?

Probably because the service uses pread64, writev, io_uring, sendfile or mmap'd I/O, none of which go through the plain read and write syscalls your program hooks. Claude flags this when it writes such a tool; the fix is to widen the hooked set or attach at a different layer.

Sources

  • Reproduced Claude Code sessions, September 2026. The refusal, the plain-observability plan, and the authorized-debugging plan quoted here are from live claude -p ... --permission-mode plan runs, captured so the refusal and the two successes could be shown side by side. The verbatim wiretapping-pattern language, the list of legitimate framings, and the payload-capture caveat are all from those transcripts.
  • Claude Code permission modes. Plan mode was used for the reproductions so the model proposed without acting, which is the mode to use when probing what an agent will and will not do.
  • bpf-helpers(7) manual page. Reference for the syscall-tracing mechanics the plans relied on, including the identity helpers a kprobe reads in the calling task's context.
  • exectop. The process-launch monitor used above to confirm an eBPF program is delivering events for the process you targeted. Observes rather than enforces.
  • yeet documentation. The JavaScript runtime for writing eBPF programs referenced as the write-it-yourself path when a ready-made tool is the wrong shape.

Related resources

Built with yeet, a JS runtime for writing eBPF programs on Linux machines. Join us on discord.