Make the pi Coding Agent Identify the Model in Commits
In November, I posted how to make Claude Code mark itself as the author of git commits. You can do the same with OpenAI Codex.
If you’re a real 20X agentic engineer, you’re probably using the pi coding agent, which can use many different model providers and models. Here’s how to make the git commit audit trail work with pi, too. (Reach out if you want help with agentic engineering.)
The funny thing about pi is that it’s somewhat self-aware of its documentation and source code – Mario Zechner pointed out I should “ask pi”
to override the bash tool instead of creating a new environment setting.
So the short answer is “just ask pi”.
So what if you don’t want to ask pi and instead see the generated code?
Because the pi agent is multi-model capable, I opted for a format like:
Pi via Claude Code (claude-sonnet-4-20250514) <noreply@anthropic.com>
The actual extension that was created from my inquiry is in ~/.pi/agent/extensions/bash-env.ts, which overrides the bash tool to first ask a registry of models and providers to assemble the environment variables needed for the git commit author line.
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { createBashTool } from "@mariozechner/pi-coding-agent";
// ## Provider identity table ##
// Add new providers here. The model ID is appended automatically:
// "Claude Code (claude-sonnet-4-20250514)"
//
const PROVIDERS: Record<string, { name: string; email: string }> = {
anthropic: { name: "Claude Code", email: "noreply@anthropic.com" },
openai: { name: "Codex", email: "codex@openai.com" },
google: { name: "Gemini CLI", email: "gemini-cli-agent@google.com" },
deepseek: { name: "DeepSeek", email: "noreply@deepseek.com" },
mistral: { name: "Mistral", email: "noreply@mistral.ai" },
};
// Fallback for providers not in the table above.
// {provider} and {model} are replaced at runtime.
const DEFAULT_IDENTITY = {
name: "{provider}",
email: "noreply@{provider}",
};
// ─────────────────────────────────────────────────────────────────────
function gitIdentity(provider: string, modelId: string) {
const entry = PROVIDERS[provider];
const name = entry?.name ?? DEFAULT_IDENTITY.name.replace("{provider}", provider);
const email = entry?.email ?? DEFAULT_IDENTITY.email.replace("{provider}", provider);
return {
GIT_AUTHOR_NAME: `Pi via ${name} (${modelId})`,
GIT_AUTHOR_EMAIL: email,
GIT_COMMITTER_NAME: `Pi via ${name} (${modelId})`,
GIT_COMMITTER_EMAIL: email,
};
}
export default function (pi: ExtensionAPI) {
const cwd = process.cwd();
let provider = "unknown";
let modelId = "unknown";
pi.on("session_start", async (_event, ctx) => {
if (ctx.model) {
provider = ctx.model.provider;
modelId = ctx.model.id;
}
});
pi.on("model_select", async (event) => {
provider = event.model.provider;
modelId = event.model.id;
});
const bashTool = createBashTool(cwd, {
spawnHook: ({ command, cwd, env }) => ({
command,
cwd,
env: { ...env, ...gitIdentity(provider, modelId) },
}),
});
pi.registerTool(bashTool);
}
With pi, you can do this in 100 different ways, most likely. I adapted the thing I knew that worked (injecting environment variables), but dedicated commit tools or skills with instructions to assemble the author information would likely also work.
If you’re interested, read the whole pi session log that I exported; it includes a bit of noise, like how the extension was first created project-locally (I didn’t expect that would be possible, so I didn’t tell pi to write it in my shared user directory), and the awful dictation result near the end. But the code works nevertheless. I’ve tried it.