Skip to content

Commit 8243ba1

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 8243ba1

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. Determine the branch's current tip SHA — for a brand-new branch this is the
1606+
base branch tip; for an existing branch, the latest commit on that branch.
1607+
3. Build a JSON payload describing the file additions and deletions in this commit:
1608+
\`\`\`bash
1609+
# Diff staged tree against the current branch tip.
1610+
# Outputs lines like: M\\tpath/to/file or D\\tpath/to/file
1611+
git diff --cached --name-status "$BRANCH_TIP_SHA" |
1612+
python3 -c '
1613+
import json, sys, base64
1614+
adds, dels = [], []
1615+
for line in sys.stdin:
1616+
status, _, path = line.strip().partition("\\t")
1617+
if status in ("A", "M", "T"):
1618+
with open(path, "rb") as f:
1619+
adds.append({"path": path, "contents": base64.b64encode(f.read()).decode()})
1620+
elif status == "D":
1621+
dels.append({"path": path})
1622+
elif status.startswith("R"):
1623+
old, new = path.split("\\t", 1)
1624+
dels.append({"path": old})
1625+
with open(new, "rb") as f:
1626+
adds.append({"path": new, "contents": base64.b64encode(f.read()).decode()})
1627+
json.dump({"additions": adds, "deletions": dels}, sys.stdout)
1628+
' > /tmp/file_changes.json
1629+
\`\`\`
1630+
4. Issue the signed-commit mutation:
1631+
\`\`\`bash
1632+
COMMIT_MESSAGE_HEADLINE="fix: resolve login redirect loop"
1633+
COMMIT_MESSAGE_BODY=$(cat <<'EOF'
1634+
Generated-By: PostHog Code
1635+
Task-Id: ${taskId}
1636+
EOF
1637+
)
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+
}'
1650+
\`\`\`
1651+
5. After the mutation returns, run \`git fetch origin "$BRANCH_NAME"\` and
1652+
\`git reset --hard "origin/$BRANCH_NAME"\` so the local checkout matches the
1653+
newly created remote commit. The next commit's parent SHA is the \`oid\`
1654+
returned by the mutation (or re-read with \`git rev-parse HEAD\` after fetch).
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)