Skip to content

feat: プロポーザルにPR関連フィールドを追加し、PR作成機能を実装#4

Merged
yu-sugimoto merged 2 commits into
mainfrom
feat/create-pr
Feb 23, 2026
Merged

feat: プロポーザルにPR関連フィールドを追加し、PR作成機能を実装#4
yu-sugimoto merged 2 commits into
mainfrom
feat/create-pr

Conversation

@yu-sugimoto

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings February 23, 2026 19:33
@yu-sugimoto yu-sugimoto merged commit a7df3f2 into main Feb 23, 2026
7 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

プロポーザルにPR関連フィールドを追加し、完了した提案の差分を適用してGitHub PRを作成する一連の機能(K8s worker + backend workflow + frontend UI)を実装するPRです。

Changes:

  • Proposalへ pr_url / pr_status を追加し、APIレスポンスとDBマイグレーションを更新
  • PR作成用のLangGraphワークフロー + K8s Job 起動 + worker(createpr) を追加
  • フロントエンドに「Create PR / View PR」導線とステータスポーリングを追加

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
k8s/secrets.yaml GitHubトークン用のSecretキーを追加
frontend/src/services/api.ts Proposal型にPRフィールド追加、createPR API呼び出し追加
frontend/src/pages/JobDetail.tsx PR作成/再試行/表示UIと作成中ポーリングを追加
docker/worker.Dockerfile PR作成に必要なGitHub CLI(gh)を追加、createpr workerを同梱
docker/worker-entrypoint.sh WORKER_MODEにcreateprを追加
docker/worker-createpr.py 変更diff適用→push→ghでPR作成するworkerを新規追加
backend/migration/versions/a1b2c3d4e5f6_add_pr_fields_to_proposals.py proposalsテーブルへ pr_url / pr_status を追加
backend/app/workflow/state.py PR作成ワークフロー用State(TypedDict)を追加
backend/app/workflow/create_pr_graph.py PR作成用LangGraph(K8s job起動・待機・成果抽出)を追加
backend/app/usecase/job_usecase.py PR作成ユースケースとバックグラウンド実行を追加
backend/app/service/k8s_service.py PR作成用K8s Job生成メソッドを追加(SecretからGITHUB_TOKEN注入)
backend/app/service/artifact_service.py pr_url.txt 読み取りメソッドを追加
backend/app/schema/job_schema.py ProposalResponseに pr_url / pr_status を追加
backend/app/router/jobs.py create-prエンドポイントを追加しレスポンスへPRフィールドを含める
backend/app/model/job.py Proposalモデルに pr_url / pr_status カラムを追加

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 91 to +99
const isInProgress = ['pending', 'analyzing', 'implementing'].includes(job.status)
const completedProposals = job.proposals.filter(
(p) => p.status === 'completed' && p.after_screenshot_url,
)

const selectedProposal =
selectedIndex !== null
? completedProposals.find((p) => p.proposal_index === selectedIndex)
: null

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completedProposalsafter_screenshot_url で絞っているため、実装側がステータスをcompletedにしてもスクリーンショットが無い提案は一覧/選択対象にならず、PR作成機能も使えません。PR作成の対象をcompleted提案全体にするなら、ここ(およびカード側)のフィルタ条件を見直してください。

Copilot uses AI. Check for mistakes.
Comment on lines +114 to +125
@router.post("/{job_id}/proposals/{proposal_index}/create-pr", response_model=ProposalResponse)
async def create_pr(
job_id: UUID,
proposal_index: int,
db: Session = Depends(get_db),
) -> ProposalResponse:
usecase = CreatePRUseCase(db)
try:
proposal = await usecase.execute(job_id, proposal_index)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
return _to_proposal_response(proposal, job_id)

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新規エンドポイント POST /api/jobs/{job_id}/proposals/{proposal_index}/create-pr が追加されていますが、backend/tests にはこのAPIの成功/失敗(未完了提案・存在しない提案・二重実行など)を検証するテストがありません。既存のTestClientベースのAPIテストに同様の形で追加して、バリデーションとステータス遷移が壊れないようにしてください。

Copilot uses AI. Check for mistakes.
Comment thread docker/worker-createpr.py
Comment on lines +104 to +115
if repo_url.startswith("https://github.com/"):
auth_repo_url = repo_url.replace(
"https://github.com/",
f"https://x-access-token:{github_token}@github.com/",
)
else:
auth_repo_url = repo_url

subprocess.run(
["git", "clone", "--depth", "1", "--branch", branch, auth_repo_url, repo_dir],
check=True,
)

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHubトークンを埋め込んだURLをそのままgit cloneに渡すと、失敗時のstderrや.git/config/git remote -v等からトークンがログに出る可能性があります。URL埋め込みは避け、http.extraHeader や GIT_ASKPASS/credential helper 等で認証するか、clone後にorigin URLをトークン無しへ戻すなどしてトークン露出を防いでください。

Suggested change
if repo_url.startswith("https://github.com/"):
auth_repo_url = repo_url.replace(
"https://github.com/",
f"https://x-access-token:{github_token}@github.com/",
)
else:
auth_repo_url = repo_url
subprocess.run(
["git", "clone", "--depth", "1", "--branch", branch, auth_repo_url, repo_dir],
check=True,
)
clone_cmd = ["git", "clone", "--depth", "1", "--branch", branch]
if repo_url.startswith("https://github.com/"):
# Use http.extraHeader to send the token instead of embedding it in the URL
clone_cmd.extend(
[
"-c",
f"http.extraHeader=Authorization: Bearer {github_token}",
]
)
clone_cmd.extend([repo_url, repo_dir])
subprocess.run(clone_cmd, check=True)

Copilot uses AI. Check for mistakes.
logs = k8s.get_job_logs(k8s_job_name)
error_msg = f"PR creation job {result}."
if logs:
error_msg += f" Logs: {logs[:500]}"

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

失敗時にジョブログ(先頭500文字)をerrorに含めると、worker側の出力に認証情報や機密(例: git cloneのURLに埋め込まれたトークン)が含まれた場合にDBへ永続化される恐れがあります。エラーメッセージには結果/ジョブ名のみを入れ、ログはサニタイズ(トークンマスク)するか、別途セキュアな場所に保存して参照する形にしてください。

Suggested change
error_msg += f" Logs: {logs[:500]}"
# Do not include raw logs in the error field to avoid leaking sensitive information.
# Log truncated logs for operational debugging instead.
logger.error(
"PR creation job %s for k8s_job_name=%s failed. Logs (first 500 chars): %s",
result,
k8s_job_name,
logs[:500],
)

Copilot uses AI. Check for mistakes.
raise ValueError("PR creation already in progress")

proposal_id = UUID(str(proposal.id))
self.proposal_repo.update_status(proposal_id, proposal.status, pr_status="creating")

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR作成を再実行する際に pr_status だけを "creating" に更新しており、以前の error_messagepr_url が残ったままになります。開始時点で error_message=Nonepr_url=None を明示的にクリアして、状態が正しく反映されるようにしてください。

Suggested change
self.proposal_repo.update_status(proposal_id, proposal.status, pr_status="creating")
self.proposal_repo.update_status(
proposal_id,
proposal.status,
pr_status="creating",
error_message=None,
pr_url=None,
)

Copilot uses AI. Check for mistakes.
UUID(proposal_id),
ProposalStatus.COMPLETED,
pr_url=result["pr_url"],
pr_status="created",

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

成功時に pr_urlpr_status は更新していますが、過去の失敗で error_message が入っている場合にクリアされません。成功時は error_message=None に更新して、UI/APIで古い失敗理由が残らないようにしてください。

Suggested change
pr_status="created",
pr_status="created",
error_message=None,

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +22
createpr)
exec python /usr/local/bin/worker-createpr.py

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the createpr worker mode wires in a PR-creation worker that reads the GITHUB_TOKEN from a Kubernetes secret and exposes it to a Claude Agent with the Bash tool enabled. Because that agent runs arbitrary shell commands based on untrusted repo contents and diff text, a malicious repository or prompt injection can cause the agent to exfiltrate GITHUB_TOKEN or perform unintended GitHub actions. To mitigate this, avoid routing PR creation through a Bash-capable agent that can see the token; instead, keep GITHUB_TOKEN confined to a non-agent process that performs git push/gh pr create deterministically without following repo-supplied instructions.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants