clown
A Nix-packaged wrapper around Claude Code that injects custom system prompts hierarchically, disables Bash by default, and supports parameterized plugin loading.
Install
Clown is a Nix flake. The bare wrapper (no plugins) can be run directly:
nix run github:amarbel-llc/clown
To build clown with plugins, use mkJuggler from your own flake (see
Plugins below).
What it does
Clown wraps the claude binary with four additions:
-
Bash disabled by default — passes
--disallowed-tools 'Bash(*)'to every invocation. -
Auto-memory disabled, idle sessions can't silently auto-continue, and Remote Control stays off — clown ships the unpatched upstream
claude-codebinary (an earlier approach patched it to read managed-settings from clown's own store path; that patch was removed) and instead passes an inline--settingsJSON block:CLAUDE_CODE_DISABLE_AUTO_MEMORY=1turns off Claude's autonomous memory writes (CLAUDE.md/AGENTS.mdfiles are unaffected), and a very largeCLAUDE_AFK_TIMEOUT_MSstops an idle confirmation prompt from auto-continuing past the user. Each of those env vars is skipped if you've already set it yourself. The same block also setsremoteControlAtStartup: false, so a session never auto-connects to Remote Control; unlike the two env vars this settings key has no paired variable to opt out with, so clown always injects it — pass claude's--remote-controlflag to turn Remote Control on for a single session. -
Hierarchical system prompt injection — walks from
$PWDup to$HOME, collecting.clown/directories along the way:- Replace mode: If any directory contains
.clown/system-prompt, the deepest one replaces Claude's system prompt entirely (--system-prompt-file). - Append mode: All
.mdfiles found in.clown/system-prompt.d/directories are concatenated shallowest-first and appended to the system prompt (--append-system-prompt-file). Builtin fragments (insystem-prompt-append.d/) are always prepended before user fragments.
- Replace mode: If any directory contains
-
Fish shell completions — full completions for all Claude Code flags, plus
clown resumeURI completion driven by theclown sessions-completesubcommand.
All other arguments are passed through to claude unchanged.
Plugins
Clown ships with zero default plugins. Consumers supply plugin flake inputs
via mkJuggler, which resolves plugin directories and burns --plugin-dir
flags into the wrapper at build time.
mkJuggler
clown.lib.${system}.mkJuggler accepts a single attribute set with a
plugins list. Each plugin definition has two fields:
| Field | Type | Description |
|-------|------|-------------|
| flake | flake input | The plugin's flake input value |
| dirs | list of strings | Paths relative to the flake's default package output. Literal paths or glob patterns. |
It returns { packages.default, devShells.default, checks }.
Consumer example
{
inputs = {
clown.url = "github:amarbel-llc/clown";
clown.inputs.nixpkgs.follows = "nixpkgs";
moxy.url = "github:amarbel-llc/moxy";
moxy.inputs.nixpkgs.follows = "nixpkgs";
bob.url = "github:amarbel-llc/bob";
bob.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, clown, moxy, bob, ... }:
let
system = "aarch64-darwin";
juggler = clown.lib.${system}.mkJuggler {
plugins = [
{ flake = moxy; dirs = [ "share/purse-first/moxy" ]; }
{ flake = bob; dirs = [ "share/purse-first/*" ]; }
];
# Optional: burn this moxy's store path into the juggler binary so
# `juggler run` launches it instead of resolving `moxy` from PATH.
# Precedence: explicit `moxy` > the first plugin flake exposing
# `packages.<system>.moxy` > PATH.
moxy = moxy.packages.${system}.moxy;
};
in {
packages.${system}.default = juggler.packages.default;
};
}
Plugin directory requirements
Each path in dirs must point to a directory containing
.claude-plugin/plugin.json with at minimum a name field. Glob patterns
(*, ?, [) are expanded at build time; non-plugin directories are
silently skipped. A glob that matches zero plugins fails the build.
Version output
clown version displays all components including dynamically loaded plugins:
COMPONENT VERSION REV
bob/bob - 474d0c4fac8a084c5378dba51337fea45b86ee2d
bob/caldav - 474d0c4fac8a084c5378dba51337fea45b86ee2d
claude-code 1.0.46 b2b9662ffe1e9a5702e7bfbd983595dd56147dbf
clown 0.0.1 edc5db5...
codex 0.0.1-beta e2dde111aea2c0699531dc616112a96cd55ab8b5
moxy/moxy - a90e0dfbc830700efe28d2238bd2acb5bf8095dc
Plugin rows use <flake-name>/<plugin-name> format. Version and rev come
from plugin.json and the flake input respectively.
For the full specification, see RFC 0001.
HTTP MCP Servers (clown.json)
Plugins can declare HTTP-based MCP servers that clown automatically launches
and manages. This enables MCP features unavailable over stdio, including
notifications/tools/list_changed and server-initiated requests.
A plugin ships a clown.json alongside .claude-plugin/:
{
"version": 1,
"httpServers": {
"my-server": {
"command": "bin/my-server",
"transport": "streamable-http",
"healthcheck": {
"path": "/healthz",
"interval": "1s",
"timeout": "30s"
}
}
}
}
Servers must implement the clown plugin protocol: bind to an ephemeral port,
print a handshake line to stdout (1|1|tcp|<addr>|streamable-http), and
respond to health checks. See RFC 0002
for the full specification.
Background monitors (clown.json)
A plugin can also declare background-shell monitors in clown.json. Each entry
mirrors Anthropic's plugin monitor schema and is injected verbatim into the
compiled .claude-plugin/plugin.json so Claude Code (≥ v2.1.105) handles
spawning, ${...} substitution, and lifecycle. Each stdout line from the
monitor command surfaces in the chat as a notification.
{
"version": 1,
"monitors": [
{
"name": "error-log",
"command": "tail -F ${CLAUDE_PLUGIN_ROOT}/logs/error.log",
"description": "Application error log"
},
{
"name": "deploy-status",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/poll-deploy.sh",
"description": "Deployment status changes",
"when": "on-skill-invoke:debug"
}
]
}
Clown does not spawn or supervise monitors — Claude Code does. It is an error
for both clown.json and the same plugin's .claude-plugin/plugin.json to
declare a monitors key; pick one source. See clown-json(5) for the full
schema and validation rules.
.clown directory
Place prompt fragments anywhere in your directory hierarchy:
~/.clown/system-prompt.d/
00-global-rules.md # applied everywhere
~/projects/.clown/system-prompt.d/
00-coding-standards.md # applied in ~/projects and below
~/projects/myapp/.clown/system-prompt
# replaces system prompt entirely for myapp
- Append fragments (
.clown/system-prompt.d/*.md) stack from shallowest to deepest — broader rules come first, project-specific ones last. - A replace file (
.clown/system-prompt) short-circuits the default system prompt. Only the deepest one wins. Append fragments still apply on top.
Building
Requires Nix with flakes enabled. Go is built and tested only inside Nix
(godyn, igloo FDR 0007/0008): the module's dependencies live in go.nix,
and there is no go.mod in the checkout.
just build # nix build --show-trace
just test-go-godyn # Go unit suite (godyn per-package lane)
just clean-result # delete result symlinks
License
MIT — see LICENSE.
:clown_face: Sasha, Head Clown