Configuration
Three keys control plugins, and all three live in the global config at ~/.config/podcode/config.json.
{
"plugin": [
"/Users/you/.config/podcode/plugins/tool-log.mjs",
"@acme/podcode-deploy"
],
"plugin_config": {
"tool-log": { "level": "warn" },
"acme-deploy": { "endpoint": "https://deploy.internal" }
},
"plugin_autodiscover": false
}plugin
An array of module specifiers, loaded in order. That order is also the hook-pipeline order, so a plugin later in the array sees the values earlier ones returned.
How a specifier is resolved depends entirely on how the string starts:
| Specifier looks like | Treated as | Resolved how |
|---|---|---|
./x.mjs, /abs/x.mjs, C:\x.mjs | File path | Resolved against the server's working directory, converted to a file:// URL, imported |
| anything else | npm specifier | Handed straight to import(), resolved by Node from the server's runtime |
Two consequences worth internalizing:
Use absolute paths. A relative specifier resolves against wherever the server happened to start, not against the config file. That directory varies, and a plugin that loads from one launch and vanishes from the next is a miserable thing to debug.
Nothing installs npm packages for you. Adding @acme/podcode-deploy to the array records the name; it does not fetch anything. A bare specifier only works if the package is already resolvable from the server process — the Settings UI reads <server cwd>/node_modules/<pkg>/package.json to show you a version, and shows none when it can't find one. For plugins you write yourself, a local file path is the practical route.
plugin_config
A map of plugin id → an arbitrary object, delivered to that plugin as the config field of server()'s second argument.
"plugin_config": {
"tool-log": { "level": "warn", "file": "/tmp/tools.log" }
}The key is the plugin's id — the string in its default export. Not the filename, not the package name. A mismatch here is quiet: the plugin loads normally and simply receives config: undefined, which is also what a plugin with no block at all receives.
plugin_autodiscover
Off by default. When set to true, the server additionally loads every .js, .mjs, .cjs, or .ts file it finds in:
<server cwd>/.podcode/plugin/
<server cwd>/.podcode/plugins/Discovered files load alphabetically, after everything in the plugin array. Each one is logged before it is imported, so an unexpected workspace plugin is visible in the log rather than silent.
Note that discovery uses the server's cwd, not the workspace you have open. A project's .podcode/plugins/ directory is not picked up unless the server was started in that project. Plugins load once, globally, and are shared by every workspace and session.
The workspace trust boundary
A project's .podcode/config.json ships inside the repository, which makes it attacker-controlled for any repo you didn't write. Six keys are therefore stripped from the project layer before it merges, and honored only from the global config:
plugin arbitrary in-process code load
plugin_autodiscover a repo could otherwise flip its own trust gate
plugin_config feeds configuration to loaded plugins
agent custom agents define tool + permission profiles
agent_autodiscover same self-flipping problem
operator_uid a repo must never claim operator identityWhat this means in practice: a plugin cannot ship with a project. You cannot check a plugin into a repo and have teammates pick it up by cloning. Each operator registers plugins in their own global config, deliberately. Whatever the project's config file says about plugins is discarded, and the drop is logged.
Configuring from the app
Settings → Plugins writes the same keys. The Add Plugin form takes an npm package name, the config editor edits that plugin's plugin_config block, and removing a plugin takes it out of the plugin array.
Every one of those changes triggers an automatic server restart — the server is unavailable for a few seconds while it comes back.
Restarting
Plugins load exactly once, at boot. Editing the config or the plugin file changes nothing in a running server. Restart from Settings → Plugins, or from the CLI:
podcodeEnvironment variables
| Variable | Default | What it does |
|---|---|---|
PODCODE_PLUGIN_LOAD_TIMEOUT_MS | 30000 | Wall-clock bound on loading all plugins at boot. On expiry the load is abandoned and the server starts with built-ins only |
The per-hook timeout — 5 seconds, applied to every individual hook call — is fixed and not configurable.