@@ -338,6 +338,7 @@ function wp_remote_retrieve_body( $response ): string {
338338
339339 // ---- createPullRequest: success path with explicit base, draft default false
340340 $ reset_http ();
341+ $ queue_response ( 200 , array () );
341342 $ queue_response ( 201 , array (
342343 'number ' => 88 ,
343344 'title ' => 'Open PR ' ,
@@ -362,17 +363,41 @@ function wp_remote_retrieve_body( $response ): string {
362363 $ assert ( 'createPullRequest exposes pull_request key ' , is_array ( $ result ) && isset ( $ result ['pull_request ' ]['number ' ] ) && 88 === $ result ['pull_request ' ]['number ' ] );
363364 $ assert ( 'createPullRequest normalized head ref ' , is_array ( $ result ) && 'feature/x ' === ( $ result ['pull_request ' ]['head ' ] ?? '' ) );
364365
365- $ call = $ GLOBALS ['dmc_http_calls ' ][0 ] ?? array ();
366+ $ call = $ GLOBALS ['dmc_http_calls ' ][1 ] ?? array ();
366367 $ body = is_string ( $ call ['args ' ]['body ' ] ?? null ) ? json_decode ( $ call ['args ' ]['body ' ], true ) : null ;
368+ $ preflight_call = $ GLOBALS ['dmc_http_calls ' ][0 ] ?? array ();
369+ $ assert ( 'createPullRequest preflights open PRs for same head/base ' , is_string ( $ preflight_call ['url ' ] ?? null ) && str_contains ( $ preflight_call ['url ' ], '/repos/owner/repo/pulls ' ) && str_contains ( $ preflight_call ['url ' ], 'head=owner%3Afeature%2Fx ' ) && str_contains ( $ preflight_call ['url ' ], 'base=main ' ) );
367370 $ assert ( 'createPullRequest posts to /repos/owner/repo/pulls ' , is_string ( $ call ['url ' ] ?? null ) && str_ends_with ( $ call ['url ' ], '/repos/owner/repo/pulls ' ) );
368371 $ assert ( 'createPullRequest forwards head and base ' , is_array ( $ body ) && 'feature/x ' === ( $ body ['head ' ] ?? '' ) && 'main ' === ( $ body ['base ' ] ?? '' ) );
369372 $ assert ( 'createPullRequest defaults maintainer_can_modify=true ' , is_array ( $ body ) && true === ( $ body ['maintainer_can_modify ' ] ?? null ) );
370373 $ assert ( 'createPullRequest does not set draft when omitted ' , is_array ( $ body ) && ! array_key_exists ( 'draft ' , $ body ) );
371- $ assert ( 'createPullRequest does not call labels endpoint without labels or agent context ' , 1 === count ( $ GLOBALS ['dmc_http_calls ' ] ) );
374+ $ assert ( 'createPullRequest does not call labels endpoint without labels or agent context ' , 2 === count ( $ GLOBALS ['dmc_http_calls ' ] ) );
375+
376+ // ---- createPullRequest: existing open PR is reused instead of POSTing a duplicate
377+ $ reset_http ();
378+ $ queue_response ( 200 , array (
379+ array (
380+ 'number ' => 188 ,
381+ 'title ' => 'Existing PR ' ,
382+ 'state ' => 'open ' ,
383+ 'html_url ' => 'https://github.com/owner/repo/pull/188 ' ,
384+ 'head ' => array ( 'ref ' => 'feature/x ' , 'sha ' => 'ccc ' ),
385+ 'base ' => array ( 'ref ' => 'main ' , 'sha ' => 'ddd ' ),
386+ ),
387+ ) );
388+ $ result = GitHubAbilities::createPullRequest ( array (
389+ 'repo ' => 'owner/repo ' ,
390+ 'title ' => 'Duplicate PR ' ,
391+ 'head ' => 'feature/x ' ,
392+ 'base ' => 'main ' ,
393+ ) );
394+ $ assert ( 'createPullRequest reuses existing open PR ' , is_array ( $ result ) && true === ( $ result ['reused ' ] ?? false ) && 188 === ( $ result ['number ' ] ?? 0 ) );
395+ $ assert ( 'createPullRequest skips POST when existing PR is reused ' , 1 === count ( $ GLOBALS ['dmc_http_calls ' ] ) && 'GET ' === ( $ GLOBALS ['dmc_http_calls ' ][0 ]['method ' ] ?? '' ) );
372396
373397 // ---- createPullRequest: agent context applies caller and provenance labels after creation
374398 $ reset_http ();
375399 PermissionHelper::$ acting_agent_slug = 'code-reviewer ' ;
400+ $ queue_response ( 200 , array () );
376401 $ queue_response ( 201 , array (
377402 'number ' => 91 ,
378403 'html_url ' => 'https://github.com/owner/repo/pull/91 ' ,
@@ -391,7 +416,7 @@ function wp_remote_retrieve_body( $response ): string {
391416 'base ' => 'main ' ,
392417 'labels ' => array ( 'needs-review ' ),
393418 ) );
394- $ label_call = $ GLOBALS ['dmc_http_calls ' ][1 ] ?? array ();
419+ $ label_call = $ GLOBALS ['dmc_http_calls ' ][2 ] ?? array ();
395420 $ label_body = is_string ( $ label_call ['args ' ]['body ' ] ?? null ) ? json_decode ( $ label_call ['args ' ]['body ' ], true ) : null ;
396421 $ assert ( 'createPullRequest labels PR through issues labels endpoint ' , is_string ( $ label_call ['url ' ] ?? null ) && str_ends_with ( $ label_call ['url ' ], '/repos/owner/repo/issues/91/labels ' ) );
397422 $ assert ( 'createPullRequest preserves caller label during post-create labeling ' , is_array ( $ label_body ) && in_array ( 'needs-review ' , $ label_body ['labels ' ] ?? array (), true ) );
@@ -402,6 +427,7 @@ function wp_remote_retrieve_body( $response ): string {
402427
403428 // ---- createPullRequest: run artifacts are committed and rendered on direct ability calls
404429 $ reset_http ();
430+ $ queue_response ( 200 , array () );
405431 $ queue_response ( 200 , array ( 'ref ' => 'refs/heads/world-day/memory ' ) );
406432 $ queue_response ( 404 , array ( 'message ' => 'Not Found ' ) );
407433 $ queue_response ( 201 , array (
@@ -441,9 +467,9 @@ function wp_remote_retrieve_body( $response ): string {
441467 'daily_memory ' => array ( 'egress ' => array ( 'bundle-file ' , 'pr-body ' ) ),
442468 ),
443469 ) );
444- $ artifact_put_call = $ GLOBALS ['dmc_http_calls ' ][2 ] ?? array ();
470+ $ artifact_put_call = $ GLOBALS ['dmc_http_calls ' ][3 ] ?? array ();
445471 $ artifact_put_body = is_string ( $ artifact_put_call ['args ' ]['body ' ] ?? null ) ? json_decode ( $ artifact_put_call ['args ' ]['body ' ], true ) : null ;
446- $ pr_call = $ GLOBALS ['dmc_http_calls ' ][3 ] ?? array ();
472+ $ pr_call = $ GLOBALS ['dmc_http_calls ' ][4 ] ?? array ();
447473 $ pr_body = is_string ( $ pr_call ['args ' ]['body ' ] ?? null ) ? json_decode ( $ pr_call ['args ' ]['body ' ], true ) : null ;
448474 $ assert ( 'createPullRequest direct ability commits bundle-file artifact before PR creation ' , 'PUT ' === ( $ artifact_put_call ['method ' ] ?? '' ) && str_contains ( (string ) ( $ artifact_put_call ['url ' ] ?? '' ), '/contents/bundles/world-creator/memory/agent/daily/2026/05/09.md ' ) );
449475 $ assert ( 'createPullRequest direct ability writes artifact to head branch ' , is_array ( $ artifact_put_body ) && 'world-day/memory ' === ( $ artifact_put_body ['branch ' ] ?? '' ) );
@@ -454,6 +480,7 @@ function wp_remote_retrieve_body( $response ): string {
454480 // ---- createPullRequest: labeling failure does not mask PR creation success
455481 $ reset_http ();
456482 PermissionHelper::$ acting_agent_slug = 'code-reviewer ' ;
483+ $ queue_response ( 200 , array () );
457484 $ queue_response ( 201 , array (
458485 'number ' => 92 ,
459486 'html_url ' => 'https://github.com/owner/repo/pull/92 ' ,
@@ -516,6 +543,7 @@ function wp_remote_retrieve_body( $response ): string {
516543
517544 // ---- createPullRequest: explicit draft and maintainer_can_modify=false
518545 $ reset_http ();
546+ $ queue_response ( 200 , array () );
519547 $ queue_response ( 201 , array (
520548 'number ' => 89 ,
521549 'head ' => array ( 'ref ' => 'feat/y ' ),
@@ -529,14 +557,15 @@ function wp_remote_retrieve_body( $response ): string {
529557 'draft ' => true ,
530558 'maintainer_can_modify ' => false ,
531559 ) );
532- $ call = $ GLOBALS ['dmc_http_calls ' ][0 ] ?? array ();
560+ $ call = $ GLOBALS ['dmc_http_calls ' ][1 ] ?? array ();
533561 $ body = is_string ( $ call ['args ' ]['body ' ] ?? null ) ? json_decode ( $ call ['args ' ]['body ' ], true ) : null ;
534562 $ assert ( 'createPullRequest forwards draft=true ' , is_array ( $ body ) && true === ( $ body ['draft ' ] ?? null ) );
535563 $ assert ( 'createPullRequest forwards maintainer_can_modify=false ' , is_array ( $ body ) && false === ( $ body ['maintainer_can_modify ' ] ?? null ) );
536564
537565 // ---- createPullRequest: missing base falls back to default branch via GET /repos
538566 $ reset_http ();
539567 $ queue_response ( 200 , array ( 'default_branch ' => 'trunk ' ) );
568+ $ queue_response ( 200 , array () );
540569 $ queue_response ( 201 , array (
541570 'number ' => 90 ,
542571 'head ' => array ( 'ref ' => 'feat/z ' ),
@@ -549,12 +578,13 @@ function wp_remote_retrieve_body( $response ): string {
549578 ) );
550579 $ assert ( 'createPullRequest fallback resolves default branch ' , is_array ( $ result ) && true === ( $ result ['success ' ] ?? false ) );
551580 $ assert ( 'createPullRequest fallback issued GET /repos/owner/repo first ' , is_string ( $ GLOBALS ['dmc_http_calls ' ][0 ]['url ' ] ?? null ) && str_contains ( $ GLOBALS ['dmc_http_calls ' ][0 ]['url ' ], '/repos/owner/repo ' ) && 'GET ' === ( $ GLOBALS ['dmc_http_calls ' ][0 ]['method ' ] ?? '' ) );
552- $ pr_call = $ GLOBALS ['dmc_http_calls ' ][1 ] ?? array ();
581+ $ pr_call = $ GLOBALS ['dmc_http_calls ' ][2 ] ?? array ();
553582 $ pr_body = is_string ( $ pr_call ['args ' ]['body ' ] ?? null ) ? json_decode ( $ pr_call ['args ' ]['body ' ], true ) : null ;
554583 $ assert ( 'createPullRequest sends fallback default base ' , is_array ( $ pr_body ) && 'trunk ' === ( $ pr_body ['base ' ] ?? '' ) );
555584
556585 // ---- createPullRequest: GitHub validation error surfaces as WP_Error
557586 $ reset_http ();
587+ $ queue_response ( 200 , array () );
558588 $ queue_response ( 422 , array ( 'message ' => 'A pull request already exists for owner:feat/x. ' ) );
559589 $ result = GitHubAbilities::createPullRequest ( array (
560590 'repo ' => 'owner/repo ' ,
0 commit comments