Skip to content

Commit ea18f32

Browse files
committed
feat(cloud): instruct agent to create signed commits via GraphQL API
Cloud agent commits land unsigned because the sandbox runs `git commit` with no signing key. Switch the cloud system prompt to instruct the agent to use GitHub's `createCommitOnBranch` mutation instead, which signs commits with the API token identity automatically.
1 parent 6551355 commit ea18f32

2 files changed

Lines changed: 105 additions & 32 deletions

File tree

packages/agent/src/server/agent-server.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,10 +890,10 @@ describe("AgentServer HTTP Mode", () => {
890890
expect(prompt).toContain(
891891
"gh pr checkout https://github.com/org/repo/pull/1",
892892
);
893+
expect(prompt).toContain("Stage your changes with `git add`");
893894
expect(prompt).toContain(
894-
"Stage and commit all changes with a clear commit message",
895+
"Create signed commits on the existing PR branch",
895896
);
896-
expect(prompt).toContain("Push to the existing PR branch");
897897
expect(prompt).not.toContain("Create a draft pull request");
898898
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
899899
});

packages/agent/src/server/agent-server.ts

Lines changed: 103 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,24 +1589,80 @@ export class AgentServer {
15891589
private buildCloudSystemPrompt(prUrl?: string | null): string {
15901590
const taskId = this.config.taskId;
15911591
const shouldAutoCreatePr = this.shouldAutoPublishCloudChanges();
1592-
const attributionInstructions = `
1593-
## Attribution
1594-
Do NOT use Claude Code's default attribution (no "Co-Authored-By" trailers, no "Generated with [Claude Code]" lines).
15951592

1596-
If you create a commit, add the following trailers to the commit message (after a blank line at the end):
1597-
Generated-By: PostHog Code
1598-
Task-Id: ${taskId}
1599-
1600-
Example:
1601-
\`\`\`
1602-
git commit -m "$(cat <<'EOF'
1603-
fix: resolve login redirect loop
1604-
1605-
Generated-By: PostHog Code
1606-
Task-Id: ${taskId}
1607-
EOF
1608-
)"
1609-
\`\`\``;
1593+
// Commits must be signed. We do NOT use `git commit` locally — instead we
1594+
// create commits via GitHub's GraphQL `createCommitOnBranch`, which signs
1595+
// commits with the API token identity automatically.
1596+
const signedCommitInstructions = `
1597+
## Creating signed commits (REQUIRED)
1598+
1599+
Do NOT use \`git commit\` or \`git push\` directly. All commits on the PR branch
1600+
MUST be created through GitHub's GraphQL API so they are signed.
1601+
1602+
For each commit you want to make:
1603+
1604+
1. Stage the files for this commit with \`git add <files>\` (do NOT run \`git commit\`).
1605+
2. Build a JSON payload describing the file additions and deletions in this commit:
1606+
\`\`\`bash
1607+
BRANCH_NAME="posthog-code/<your-branch>"
1608+
# Resolve the branch's current tip SHA from the remote. For the first commit
1609+
# on a new branch this returns the base SHA you pushed in the empty-ref step.
1610+
BRANCH_TIP_SHA=$(git ls-remote origin "$BRANCH_NAME" | cut -f1)
1611+
1612+
# Diff staged tree against the current branch tip.
1613+
# Outputs lines like: M\\tpath/to/file or D\\tpath/to/file
1614+
git diff --cached --name-status "$BRANCH_TIP_SHA" |
1615+
node -e '
1616+
const fs = require("fs");
1617+
const adds = [], dels = [];
1618+
const lines = fs.readFileSync(0, "utf8").split("\\n").filter(Boolean);
1619+
for (const line of lines) {
1620+
const [status, ...rest] = line.split("\\t");
1621+
if (["A", "M", "T"].includes(status)) {
1622+
adds.push({ path: rest[0], contents: fs.readFileSync(rest[0]).toString("base64") });
1623+
} else if (status === "D") {
1624+
dels.push({ path: rest[0] });
1625+
} else if (status.startsWith("R")) {
1626+
dels.push({ path: rest[0] });
1627+
adds.push({ path: rest[1], contents: fs.readFileSync(rest[1]).toString("base64") });
1628+
}
1629+
}
1630+
process.stdout.write(JSON.stringify({ additions: adds, deletions: dels }));
1631+
' > /tmp/file_changes.json
1632+
\`\`\`
1633+
3. Issue the signed-commit mutation:
1634+
\`\`\`bash
1635+
COMMIT_MESSAGE_HEADLINE="fix: resolve login redirect loop"
1636+
COMMIT_MESSAGE_BODY="Generated-By: PostHog Code
1637+
Task-Id: ${taskId}"
1638+
gh api graphql -F repo="$GITHUB_REPOSITORY" -F branch="$BRANCH_NAME" \\
1639+
-F oid="$BRANCH_TIP_SHA" -F headline="$COMMIT_MESSAGE_HEADLINE" \\
1640+
-F body="$COMMIT_MESSAGE_BODY" -F changes=@/tmp/file_changes.json \\
1641+
-f query='
1642+
mutation($repo:String!,$branch:String!,$oid:GitObjectID!,$headline:String!,$body:String!,$changes:FileChanges!){
1643+
createCommitOnBranch(input:{
1644+
branch:{repositoryNameWithOwner:$repo, branchName:$branch}
1645+
expectedHeadOid:$oid
1646+
message:{headline:$headline, body:$body}
1647+
fileChanges:$changes
1648+
}){ commit { oid url } }
1649+
}' --jq '.data.createCommitOnBranch.commit.oid'
1650+
\`\`\`
1651+
The mutation prints the new commit's OID. Use it as \`$BRANCH_TIP_SHA\` for
1652+
your next commit. Do NOT run \`git reset --hard\` or otherwise overwrite the
1653+
local working tree — your in-progress unstaged changes (intended for a later
1654+
commit) must be preserved.
1655+
1656+
Notes:
1657+
- The branch must already exist on the remote. To create a new branch, push an
1658+
empty ref first: \`git push origin "$BASE_SHA:refs/heads/$BRANCH_NAME"\`
1659+
where \`$BASE_SHA\` is the base-branch tip.
1660+
- Every commit you create through this flow will be marked "Verified" on GitHub
1661+
and authored by the GitHub App associated with the token. Do not attempt to
1662+
override the author identity.
1663+
- Do NOT add \`Co-Authored-By\` trailers or \`Generated with [Claude Code]\` lines.
1664+
Use only the \`Generated-By: PostHog Code\` and \`Task-Id: ${taskId}\` trailers
1665+
shown above.`;
16101666

16111667
if (prUrl) {
16121668
if (!shouldAutoCreatePr) {
@@ -1620,7 +1676,7 @@ Do the requested work, but stop with local changes ready for review.
16201676
Important:
16211677
- Do NOT create new commits, push to the branch, or update the pull request unless the user explicitly asks.
16221678
- Do NOT create a new branch or a new pull request.
1623-
${attributionInstructions}
1679+
${signedCommitInstructions}
16241680
`;
16251681
}
16261682

@@ -1631,12 +1687,16 @@ This task already has an open pull request: ${prUrl}
16311687
16321688
After completing the requested changes:
16331689
1. Check out the existing PR branch with \`gh pr checkout ${prUrl}\`
1634-
2. Stage and commit all changes with a clear commit message
1635-
3. Push to the existing PR branch
1690+
2. Stage your changes with \`git add\` (do NOT run \`git commit\`)
1691+
3. Create signed commits on the existing PR branch using the GitHub GraphQL
1692+
flow described below. The branch already exists, so skip the "create branch"
1693+
step — use the current PR branch tip as the first \`oid\`.
16361694
16371695
Important:
16381696
- Do NOT create a new branch or a new pull request.
1639-
${attributionInstructions}
1697+
- Do NOT use \`git commit\` or \`git push\`; commits must be created via
1698+
\`createCommitOnBranch\` so they are signed.
1699+
${signedCommitInstructions}
16401700
`;
16411701
}
16421702

@@ -1651,8 +1711,12 @@ When the user asks for code changes:
16511711
When the user explicitly asks to clone or work in a GitHub repository:
16521712
- Clone the repository into /tmp/workspace/repos/<owner>/<repo> using \`gh repo clone <owner>/<repo> /tmp/workspace/repos/<owner>/<repo>\`
16531713
- Work from inside that cloned repository for follow-up code changes
1654-
- If the user explicitly asks you to open or update a pull request, create a branch, commit the requested changes, push it, and open a draft pull request from inside the clone
1655-
- Do NOT create branches, commits, push changes, or open pull requests unless the user explicitly asks for that`;
1714+
- If the user explicitly asks you to open or update a pull request, create a
1715+
branch (by pushing an empty ref), then create signed commits on it via
1716+
\`createCommitOnBranch\` (see below), and open a draft pull request
1717+
- Do NOT create branches, commits, push changes, or open pull requests unless the user explicitly asks for that
1718+
- Do NOT use \`git commit\` or \`git push\` for commit creation; commits must be
1719+
created via the GitHub GraphQL API so they are signed`;
16561720

16571721
return `
16581722
# Cloud Task Execution — No Repository Mode
@@ -1671,7 +1735,7 @@ ${publishInstructions}
16711735
16721736
Important:
16731737
- Prefer using MCP tools to answer questions with real data over giving generic advice.
1674-
${attributionInstructions}
1738+
${signedCommitInstructions}
16751739
`;
16761740
}
16771741

@@ -1683,26 +1747,35 @@ Do the requested work, but stop with local changes ready for review.
16831747
16841748
Important:
16851749
- Do NOT create a branch, commit, push, or open a pull request unless the user explicitly asks.
1686-
${attributionInstructions}
1750+
${signedCommitInstructions}
16871751
`;
16881752
}
16891753

16901754
return `
16911755
# Cloud Task Execution
16921756
16931757
After completing the requested changes:
1694-
1. Create a new branch prefixed with \`posthog-code/\` (e.g. \`posthog-code/fix-login-redirect\`) based on the work done
1695-
2. Stage and commit all changes with a clear commit message
1696-
3. Push the branch to origin
1697-
4. Create a draft pull request using \`gh pr create --draft${this.config.baseBranch ? ` --base ${this.config.baseBranch}` : ""}\` with a descriptive title and body. Add the following footer at the end of the PR description:
1758+
1. Pick a branch name prefixed with \`posthog-code/\` (e.g. \`posthog-code/fix-login-redirect\`)
1759+
2. Create the branch on the remote by pushing an empty ref:
1760+
\`\`\`bash
1761+
BASE_SHA=$(git rev-parse HEAD)
1762+
git push origin "$BASE_SHA:refs/heads/posthog-code/<your-branch>"
1763+
\`\`\`
1764+
3. Stage your changes with \`git add\` (do NOT run \`git commit\`)
1765+
4. Create signed commits on the new branch using the GitHub GraphQL flow
1766+
described below. Use \`$BASE_SHA\` as the first commit's \`oid\`.
1767+
5. Create a draft pull request using \`gh pr create --draft${this.config.baseBranch ? ` --base ${this.config.baseBranch}` : ""}\` with a descriptive title and body. Add the following footer at the end of the PR description:
16981768
\`\`\`
16991769
---
17001770
*Created with [PostHog Code](https://posthog.com/code?ref=pr)*
17011771
\`\`\`
17021772
17031773
Important:
17041774
- Always create the PR as a draft. Do not ask for confirmation.
1705-
${attributionInstructions}
1775+
- Do NOT use \`git commit\` or \`git push <branch>\` for commit creation; commits
1776+
must be created via \`createCommitOnBranch\` so they are signed. The only
1777+
\`git push\` allowed is the empty-ref push in step 2 to create the branch.
1778+
${signedCommitInstructions}
17061779
`;
17071780
}
17081781

0 commit comments

Comments
 (0)