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:
parent
d5045d622c
commit
bc5c392331
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue