Skip to content

fix(mcp): gittensory_propose_action stages actions with no expectedHeadSha, disabling the force-push guard #2255

Description

@JSONbored

Parent: #1936

Problem

proposeAction() (src/mcp/server.ts:2442-2466) builds the pending-action params from only label, reviewBody, mergeMethod, and closeComment — it never fetches or records the PR's live head SHA before staging. Compare to the webhook/sweep staging path (stageForApproval/actionParams, src/services/agent-action-executor.ts:227-256), which always persists action.expectedHeadSha from the reviewed head. Every action staged through the MCP tool has params.expectedHeadSha === undefined.

Failure scenario: a maintainer calls gittensory_propose_action to stage a merge on a PR. The contributor force-pushes different code. When the action is later accepted (via REST or gittensory_decide_pending_action), the supersede check in decidePendingAgentAction (src/services/agent-approval-queue.ts:44-45) short-circuits false because stagedHead is undefined — the "superseded — force-push after staging" rejection never fires. The executor's own freshness fallback then trivially matches whatever is live at accept time, so unreviewed force-pushed code merges with no freshness rejection at all — exactly the threat the guard's own code comments say it exists to stop.

Requirements

  • Every action staged via MCP must be pinned to the head SHA that was actually reviewed at proposal time, matching the invariant the webhook/sweep path already upholds.

Deliverables

  • In proposeAction(), fetch the PR's live head via getPullRequest before building params, and include expectedHeadSha: pr?.headSha in the staged params (or reject the proposal if the PR can't be fetched).
  • Add a regression test: stage an action via proposeAction, force-push the PR, accept — assert the accept is superseded/rejected, not executed.

Acceptance criteria

  • An MCP-staged action force-pushed after staging is rejected on accept, matching webhook/sweep-staged actions.
  • MCP-staged actions accepted with the head unchanged still execute normally.

Expected outcome

MCP-originated staged actions get the same force-push protection every other staging path already has.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions