Pi Extension Authoring

SkillCommunication

Build, debug, and maintain Pi extensions safely. Use when editing ~/.pi/agent/extensions, joelclaw/pi/extensions, pi-tools extensions, or any code that uses the Pi ExtensionAPI, hooks, tools, commands, widgets, session replacement, reload, custom messages, or extension package updates.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Pi Extension Authoring skill

What this skill tells your AI

The instructions your AI receives, as published by joelhooks/joelclaw in skills/pi-extension-authoring/SKILL.md and read by ahel’s review.

Use this before changing Pi extension code or debugging Pi startup warnings.

Canonical surfaces

  • Repo-local joelclaw extensions: ~/Code/joelhooks/joelclaw/pi/extensions/<name>
  • Active symlinks: ~/.pi/agent/extensions/<name> → repo-local source
  • Package extensions: ~/.pi/agent/git/github.com/<owner>/<repo>
  • Package config: ~/.pi/agent/settings.json
  • Pi SDK/docs: global package under ~/.local/share/fnm/node-versions/*/installation/lib/node_modules/@earendil-works/pi-coding-agent/ (older installs used @mariozechner/pi-coding-agent)

Rules

  1. Do not keep using captured session-bound objects after session replacement.

    • ctx.newSession(), ctx.fork(), ctx.switchSession(), and ctx.reload() invalidate old pi and ctx objects.
    • For replacement commands, move post-switch work into the method's withSession callback and use only the callback ctx.
    • For reload commands, treat await ctx.reload(); return; as terminal.
  2. Do not call pi.sendMessage() from delayed session_start callbacks.

    • A timer, promise, or subprocess can finish after reload/session replacement and throw stale-context errors.
    • Start async work without storing pi/ctx, cache the plain result, then inject through before_agent_start by returning { message }.
    • Keep any first-turn wait tiny; use a short Promise.race budget if the result may not be ready.
  3. Use pi.registerTool(), not old APIs.

    • pi.addTool() is gone.
    • Tool results need cloneable details. No timers, functions, sockets, or class instances.
  4. Optional dependencies must be lazy.

    • No top-level imports for optional packages.
    • Use dynamic import or createRequire() inside guarded functions.
    • Missing optional dependencies should disable that feature, not crash Pi startup.
  5. One active copy per extension.

    • Duplicate extension paths cause double hooks, duplicate messages, and weird cost/trace behavior.
    • If a package checkout still contains an old extension directory, make sure its package.json no longer lists it in pi.extensions before ignoring it.

Session-safe hidden message pattern

let cachedMessage: HiddenCustomMessage | null | undefined;
let messagePromise: Promise<HiddenCustomMessage | null> | null = null;
let delivered = false;

pi.on("session_start", (_event, ctx) => {
  delivered = false;
  cachedMessage = undefined;
  const sessionId = ctx.sessionManager?.getSessionId?.() ?? null;
  messagePromise = buildMessageWithoutCapturedPi(sessionId).then((message) => {
    cachedMessage = message;
    return message;
  });
});

pi.on("before_agent_start", async (event) => {
  const message = await Promise.race([
    messagePromise,
    new Promise<null>((resolve) => setTimeout(() => resolve(null), 250)),
  ]);

  if (message && !delivered) {
    delivered = true;
    return {
      systemPrompt: event.systemPrompt,
      message,
    };
  }

  return { systemPrompt: event.systemPrompt };
});

Validation

Run the cheap gates first:

bun --check path/to/extension/index.ts
PI_OFFLINE=1 pi --help >/tmp/pi-help.out 2>/tmp/pi-help.err
rg -n "Skill conflicts|cannot load optional dependency|stale after session|<extension-name>" /tmp/pi-help.err /tmp/pi-help.out

For package updates:

# Update Pi plus settings-managed packages. Current Pi self-update migrates
# @mariozechner/pi-coding-agent installs to @earendil-works/pi-coding-agent.
pi update

# Keep npm-global tools current. Do not install the unrelated `pi` npm package.
npm install -g @earendil-works/pi-coding-agent@latest pi-mcp-adapter@latest pi-subagents@latest pi-interactive-shell@latest

# If Bun's shadow global is present, either point ~/.bun/bin/pi at ~/.local/bin/pi
# or force the exact current version. Bun may block fresh releases via minimum-release-age.
bun add -g --minimum-release-age=0 @earendil-works/pi-coding-agent@$(pi --version)

which -a pi
pi --version
pi list
npm list -g --depth=0 | rg '@earendil-works/pi-coding-agent|@mariozechner/pi-coding-agent|pi-mcp-adapter|pi-subagents|pi-interactive-shell'

Smoke-test both base Pi and full extension/tool startup with an approved model:

pi -p --no-session --no-tools --no-extensions --no-context-files --model openai-codex/gpt-5.6-sol "Reply with exactly OK."
pi -p --no-session --no-context-files --model openai-codex/gpt-5.6-sol "Reply with exactly OK."

Clean untracked npm lockfiles generated inside git package checkouts when the package does not track them.

If multiple pi binaries exist, verify they resolve to the same version. On Panda, ~/.bun/bin/pi should be a symlink to ~/.local/bin/pi; Bun's global installer can lag behind npm because of minimum-release-age.

Common fixes

SymptomLikely causeFix
This extension ctx is stale after session replacement or reloadDelayed callback used old pi/ctxCache plain data; inject via before_agent_start return or use withSession
cannot load optional dependencyPackage not installed from extension realpathInstall deps from the source repo realpath or make the dependency lazy/fail-open
Duplicate startup messages/tracesExtension loaded twice via package + symlinkRemove one active path from settings/package pi.extensions
Skill conflict warningSkill directory name differs from frontmatter nameRename directory or frontmatter so they match

Signals

GitHub stars
64
Forks
2
Last commit
Sep 2026

ahel review

  • K1binfo
    installs-packages

Automated review, not a security audit. Ruleset v1+k2.

Advanced
Item type
skill
Key
pi-extension-authoring
Source
github.com/joelhooks/joelclaw