mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
fix(agents): hired OpenCode agents inherit the hiring agent's OpenRouter key reference
An OpenCode agent hired by another OpenCode agent got no credential, so approving the hire left it failing verification with harness_auth_failed. Credential inheritance covered Claude, Codex and Grok but not opencode_local. Add OPENROUTER_API_KEY, the key AI connections bind for opencode_local. Only secret references are copied; plain values such as OPENCODE_CONFIG_CONTENT still stay with the parent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
839e3c37e0
commit
b4b720601a
2 files changed
+25
No files matched your search
@@ -265,6 +265,28 @@ describeEmbeddedPostgres("hired agent provider credential inheritance", () => {
|
||||
expect(childEnvOf(res).XAI_API_KEY).toMatchObject({ type: "secret_ref", secretId: secret.id });
|
||||
});
|
||||
|
||||
it("inherits a native OpenCode parent's OPENROUTER_API_KEY reference when the hire inherits the caller runtime", async () => {
|
||||
const companyId = await seedCompany();
|
||||
await db.update(companies).set({ requireBoardApprovalForNewAgents: true }).where(eq(companies.id, companyId));
|
||||
const secret = await createCompanySecret(companyId, "openrouter-parent-key");
|
||||
const parent = await seedParentAgent(companyId, "paperclip_runner", {});
|
||||
await db.update(agents).set({ adapterConfig: {
|
||||
provider: "opencode", model: "openrouter/deepseek/deepseek-v4.1-flash-1017", opencodePermissionMode: "allow",
|
||||
env: { OPENROUTER_API_KEY: secretRef(secret.id, { version: "latest" }), OPENCODE_CONFIG_CONTENT: "{\"provider\":{}}" },
|
||||
} }).where(eq(agents.id, parent.id));
|
||||
|
||||
const res = await hire(agentActor(companyId, parent.id), companyId, {
|
||||
name: "OpenCode Child", role: "qa", adapterType: "paperclip_runner", inheritRuntimeFrom: "caller",
|
||||
});
|
||||
|
||||
expect(res.status, JSON.stringify(res.body)).toBe(201);
|
||||
expect(res.body.agent.status).toBe("pending_approval");
|
||||
const [child] = await db.select().from(agents).where(eq(agents.id, res.body.agent.id));
|
||||
const env = (child!.adapterConfig as { env?: Record<string, unknown> }).env ?? {};
|
||||
expect(env.OPENROUTER_API_KEY).toMatchObject({ type: "secret_ref", secretId: secret.id, version: "latest" });
|
||||
expect(env.OPENCODE_CONFIG_CONTENT).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each(["create", "hire"] as const)("blocks native Claude credential redirection before %s inheritance", async mode => {
|
||||
const companyId = await seedCompany();
|
||||
const secret = await createCompanySecret(companyId, "fixture-parent-credential");
|
||||
|
||||
@@ -2302,6 +2302,9 @@ export function agentRoutes(
|
||||
claude_local: ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN"],
|
||||
codex_local: ["OPENAI_API_KEY", "CODEX_API_KEY"],
|
||||
grok_local: ["XAI_API_KEY"],
|
||||
// OpenCode reaches its models through OpenRouter, the provider account
|
||||
// AI connections bind for opencode_local.
|
||||
opencode_local: ["OPENROUTER_API_KEY"],
|
||||
};
|
||||
|
||||
function isInheritableCredentialReference(value: unknown): value is Record<string, unknown> {
|
||||
|
||||
Reference in new issue
Block a user