fix(server): stop inferring PR credential preflight from issue text (#10755)

## Thinking Path

> - Paperclip coordinates agent runs and checks required runtime
credentials before dispatch.
> - The push-capability preflight protects runs that use the GitHub PR
workflow skill.
> - PR #10659 also made issue title and description text trigger this
preflight.
> - That text heuristic blocks tasks that mention a pull request but do
not need a bound GitHub token at dispatch time.
> - This pull request removes the text heuristic and keeps the explicit
skill trigger.
> - The benefit is that ordinary task wording no longer causes a false
configuration failure.

## Linked Issues or Issue Description

Refs: #10659

**What happened?**

An issue title or description that said to open a pull request or push a
branch could trigger the push-capability credential preflight. The run
then failed before dispatch when no project-level or agent-level GitHub
token was bound, even when the task could proceed without that
preflight.

**Expected behavior**

The preflight must run only when the issue explicitly selects the GitHub
PR workflow skill. Issue prose alone must not enable the guard.

**Steps to reproduce**

1. Assign a local Codex or Claude agent an issue that says to open a
pull request.
2. Do not attach the GitHub PR workflow skill to the issue.
3. Start the run without a project-level or agent-level GitHub token
binding.
4. Observe the false `push_write_credential_missing` failure before this
fix.

**Paperclip version or commit**

`master` after PR #10659.

**Deployment mode**

Local dev with a git-sensitive local adapter.

## What Changed

- Removed `issueTextImpliesPrDeliverable` and its text-pattern matcher.
- Restored `requiresPushCapabilityPreflight` to use only explicit GitHub
PR workflow skill keys.
- Removed the obsolete issue-text tests while keeping coverage for the
explicit skill, adapter, and issue gates.

## Verification

- `PAPERCLIP_LOG_DIR=<run-owned-dir> pnpm vitest run
server/src/__tests__/heartbeat-workspace-session.test.ts` — 134 tests
passed.
- `pnpm --filter @paperclipai/server typecheck` — passed.
- `git diff --check origin/master...HEAD` — passed.

## Risks

- Low risk. A task that states a PR deliverable but does not select the
GitHub PR workflow skill will no longer receive the early credential
preflight. This is the intended temporary behavior.
- Tasks that explicitly select the skill still receive the existing
credential and checkout checks.

> This change does not add a core feature and does not overlap with
ROADMAP.md work.

## Model Used

OpenAI Codex with model ID `gpt-5`. The runtime did not expose the
context-window size. The model used agentic reasoning, tool use, and
code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change and contains no internal
Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-08-03 10:21:33 -05:00 committed by GitHub
parent d5045d622c
commit bc5c392331
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 1 additions and 84 deletions

View File

@ -30,7 +30,6 @@ import {
resolveExecutionWorkspaceReuseProvisioningPolicy,
resolveNextSessionState,
resolveTaskSessionConfigFreshness,
issueTextImpliesPrDeliverable,
isWorkspaceSyncConflictFailure,
requiresPushCapabilityPreflight,
resolveWorkspaceAfterLowTrustPreflight,
@ -972,58 +971,7 @@ describe("assertPushCapabilityCheckoutValid", () => {
});
});
describe("issueTextImpliesPrDeliverable", () => {
it("matches verb-anchored PR deliverables", () => {
expect(issueTextImpliesPrDeliverable("Review and open PR for the CI shard split")).toBe(true);
expect(issueTextImpliesPrDeliverable("Push the branch and open a pull request")).toBe(true);
expect(issueTextImpliesPrDeliverable("Each run: make the change and open a draft PR")).toBe(true);
expect(issueTextImpliesPrDeliverable("push feature work to origin when done")).toBe(true);
});
it("ignores passing mentions and unrelated text", () => {
expect(issueTextImpliesPrDeliverable("The PR merged yesterday; investigate the regression")).toBe(false);
expect(issueTextImpliesPrDeliverable("PR feedback addressed")).toBe(false);
expect(issueTextImpliesPrDeliverable("Update the pricing page copy")).toBe(false);
expect(issueTextImpliesPrDeliverable("a proper approach to pushing back on scope")).toBe(false);
expect(issueTextImpliesPrDeliverable(null)).toBe(false);
expect(issueTextImpliesPrDeliverable("")).toBe(false);
});
it("ignores non-git uses of push", () => {
expect(issueTextImpliesPrDeliverable("push back on the upstream dependency change")).toBe(false);
expect(issueTextImpliesPrDeliverable("push back the branch cut date")).toBe(false);
expect(issueTextImpliesPrDeliverable("push notifications for mobile")).toBe(false);
// Git shapes still match.
expect(issueTextImpliesPrDeliverable("pushing the release branch")).toBe(true);
expect(issueTextImpliesPrDeliverable("push feature work to origin when done")).toBe(true);
});
});
describe("requiresPushCapabilityPreflight", () => {
it("enables the guard when the issue text states the PR deliverable", () => {
expect(requiresPushCapabilityPreflight({
adapterType: "codex_local",
issueId: "issue-1",
explicitRunScopedSkillKeys: [],
issueText: "Push ci/shard-split and open PR",
})).toBe(true);
expect(requiresPushCapabilityPreflight({
adapterType: "codex_local",
issueId: "issue-1",
explicitRunScopedSkillKeys: [],
issueText: "Investigate why the PR checks were slow",
})).toBe(false);
// Without an issue there is nothing to preflight.
expect(requiresPushCapabilityPreflight({
adapterType: "codex_local",
issueId: null,
explicitRunScopedSkillKeys: [],
issueText: "open a PR",
})).toBe(false);
});
it("only enables the guard when the issue explicitly mentions the GitHub PR workflow skill", () => {
expect(requiresPushCapabilityPreflight({
adapterType: "codex_local",

View File

@ -736,44 +736,14 @@ function hasGithubPrWorkflowSkill(desiredSkills: string[]) {
});
}
/**
* Conservative, verb-anchored patterns for an issue whose deliverable is a
* pushed branch or opened pull request. Verb anchoring keeps passing mentions
* ("the PR merged yesterday") from triggering the credential preflight.
*/
const PR_DELIVERABLE_TEXT_PATTERNS = [
/\bopen(?:s|ed|ing)?\s+(?:a\s+|the\s+|an?\s+draft\s+)?(?:pull\s+request|pr)\b/i,
/\b(?:create|creates|created|creating|raise|raises|raised|raising|submit|submits|submitted|submitting)\s+(?:a\s+|the\s+|an?\s+draft\s+)?(?:pull\s+request|pr)\b/i,
// "push back" (an objection or a date) is never a git push, and bare
// proximity to words like "upstream" over-matches ("push back on the
// upstream dependency change"); require the git object shape instead.
/\bpush(?:es|ed|ing)?\b(?!\s+back\b)[^.\n]{0,40}\bbranch(?:es)?\b/i,
/\bpush(?:es|ed|ing)?\s+(?:[^.\n]{0,30}\s)?to\s+(?:origin|remote|upstream|github)\b/i,
];
export function issueTextImpliesPrDeliverable(text: string | null | undefined): boolean {
if (!text) return false;
return PR_DELIVERABLE_TEXT_PATTERNS.some((pattern) => pattern.test(text));
}
export function requiresPushCapabilityPreflight(input: {
adapterType: string;
issueId: string | null | undefined;
explicitRunScopedSkillKeys: string[];
/**
* Issue title + description. Routine-created issues and agent-to-agent
* handoffs rarely mention the GitHub PR workflow skill explicitly, yet
* state the PR deliverable in plain text without this, the credential
* gap only surfaces after the implementation and review work is done.
*/
issueText?: string | null;
}) {
return Boolean(input.issueId)
&& GIT_SENSITIVE_LOCAL_ADAPTER_TYPES.has(input.adapterType)
&& (
hasGithubPrWorkflowSkill(input.explicitRunScopedSkillKeys)
|| issueTextImpliesPrDeliverable(input.issueText)
);
&& hasGithubPrWorkflowSkill(input.explicitRunScopedSkillKeys);
}
const LOW_TRUST_SENSITIVE_ENV_KEY_RE =
@ -13840,7 +13810,6 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
adapterType: agent.adapterType,
issueId,
explicitRunScopedSkillKeys: runScopedMentionedSkillKeys,
issueText: issueRef ? `${issueRef.title ?? ""}\n${issueRef.description ?? ""}` : null,
});
const { resolvedConfig, secretKeys, secretManifest } = await resolveExecutionRunAdapterConfig({
companyId: agent.companyId,