@@ -16,7 +16,12 @@ import {
1616import { type ServerType , serve } from "@hono/node-server" ;
1717import { execGh } from "@posthog/git/gh" ;
1818import { getCurrentBranch } from "@posthog/git/queries" ;
19- import type { Adapter } from "@posthog/shared" ;
19+ import {
20+ type Adapter ,
21+ buildPrOutput ,
22+ mergePrUrls ,
23+ readPrUrls ,
24+ } from "@posthog/shared" ;
2025import { unzipSync } from "fflate" ;
2126import { Hono } from "hono" ;
2227import { z } from "zod" ;
@@ -45,7 +50,11 @@ import type { PermissionMode } from "../execution-mode";
4550import { DEFAULT_CODEX_MODEL , fetchGatewayModels } from "../gateway-models" ;
4651import { HandoffCheckpointTracker } from "../handoff-checkpoint" ;
4752import { PostHogAPIClient } from "../posthog-api" ;
48- import { findPrUrl , wasCreatedRecently } from "../pr-url-detector" ;
53+ import {
54+ findPrUrls ,
55+ wasCreatedByLogin ,
56+ wasCreatedRecently ,
57+ } from "../pr-url-detector" ;
4958import {
5059 formatConversationForResume ,
5160 type ResumeState ,
@@ -3438,13 +3447,14 @@ ${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}
34383447 update : Record < string , unknown > | undefined ,
34393448 ) : void {
34403449 if ( ! update ) return ;
3441- const prUrl = findPrUrl ( JSON . stringify ( update ) ) ;
3442- if ( ! prUrl || this . evaluatedPrUrls . has ( prUrl ) ) return ;
3443- this . evaluatedPrUrls . add ( prUrl ) ;
3444- // Chain so attributions run in detection order; later PRs overwrite earlier ones.
3445- this . prAttributionChain = this . prAttributionChain
3446- . catch ( ( ) => { } )
3447- . then ( ( ) => this . attachPrIfCreatedThisRun ( payload , prUrl ) ) ;
3450+ for ( const prUrl of findPrUrls ( JSON . stringify ( update ) ) ) {
3451+ if ( this . evaluatedPrUrls . has ( prUrl ) ) continue ;
3452+ this . evaluatedPrUrls . add ( prUrl ) ;
3453+ // Chain so attributions run in detection order; later PRs append after earlier ones.
3454+ this . prAttributionChain = this . prAttributionChain
3455+ . catch ( ( ) => { } )
3456+ . then ( ( ) => this . attachPrIfCreatedThisRun ( payload , prUrl ) ) ;
3457+ }
34483458 }
34493459
34503460 private async attachPrIfCreatedThisRun (
@@ -3454,9 +3464,13 @@ ${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}
34543464 // Already the attributed PR (e.g. seeded from a Slack notification, or re-detected).
34553465 if ( prUrl === this . detectedPrUrl ) return ;
34563466
3457- let createdAt : string | null ;
3467+ let attribution : { createdAt : string | null ; author : string | null } ;
3468+ let ghLogin : string | null ;
34583469 try {
3459- createdAt = await this . fetchPrCreatedAt ( prUrl ) ;
3470+ [ attribution , ghLogin ] = await Promise . all ( [
3471+ this . fetchPrAttribution ( prUrl ) ,
3472+ this . fetchGhLogin ( ) ,
3473+ ] ) ;
34603474 } catch ( err ) {
34613475 this . logger . debug ( "PR attribution lookup failed" , {
34623476 runId : payload . run_id ,
@@ -3466,14 +3480,21 @@ ${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}
34663480 return ;
34673481 }
34683482
3469- // Only attribute PRs created during this run, not ones the agent merely viewed.
3470- if ( ! wasCreatedRecently ( createdAt , Date . now ( ) ) ) return ;
3483+ // Only attribute PRs created during this run by this run's own GitHub
3484+ // identity — not ones the agent merely viewed.
3485+ if ( ! wasCreatedRecently ( attribution . createdAt , Date . now ( ) ) ) return ;
3486+ if ( ! wasCreatedByLogin ( attribution . author , ghLogin ) ) return ;
34713487
34723488 this . detectedPrUrl = prUrl ;
34733489
34743490 try {
3491+ const freshOutput = await this . posthogAPI
3492+ . getTaskRun ( payload . task_id , payload . run_id )
3493+ . then ( ( run ) => run . output )
3494+ . catch ( ( ) => null ) ;
3495+ const urls = mergePrUrls ( readPrUrls ( freshOutput ) , [ prUrl ] ) ;
34753496 await this . posthogAPI . updateTaskRun ( payload . task_id , payload . run_id , {
3476- output : { pr_url : prUrl } ,
3497+ output : buildPrOutput ( freshOutput , urls ) ,
34773498 } ) ;
34783499 this . logger . debug ( "Attributed created PR to task run" , {
34793500 taskId : payload . task_id ,
@@ -3490,21 +3511,50 @@ ${signedCommitInstructions}${prLinkInstructions}${shellEfficiencyInstructions}
34903511 }
34913512 }
34923513
3493- private async fetchPrCreatedAt ( prUrl : string ) : Promise < string | null > {
3494- const res = await execGh ( [ "pr" , "view" , prUrl , "--json" , "createdAt" ] , {
3495- cwd : this . config . repositoryPath ,
3496- timeoutMs : 10_000 ,
3497- } ) ;
3498- if ( res . exitCode !== 0 ) return null ;
3514+ private async fetchPrAttribution (
3515+ prUrl : string ,
3516+ ) : Promise < { createdAt : string | null ; author : string | null } > {
3517+ const res = await execGh (
3518+ [ "pr" , "view" , prUrl , "--json" , "createdAt,author" ] ,
3519+ {
3520+ cwd : this . config . repositoryPath ,
3521+ timeoutMs : 10_000 ,
3522+ } ,
3523+ ) ;
3524+ if ( res . exitCode !== 0 ) return { createdAt : null , author : null } ;
34993525 try {
3500- return (
3501- ( JSON . parse ( res . stdout ) as { createdAt ?: string } ) . createdAt ?? null
3502- ) ;
3526+ const data = JSON . parse ( res . stdout ) as {
3527+ createdAt ?: string ;
3528+ author ?: { login ?: string } ;
3529+ } ;
3530+ return {
3531+ createdAt : data . createdAt ?? null ,
3532+ author : data . author ?. login ?? null ,
3533+ } ;
35033534 } catch {
3504- return null ;
3535+ return { createdAt : null , author : null } ;
35053536 }
35063537 }
35073538
3539+ private ghLoginPromise : Promise < string | null > | null = null ;
3540+
3541+ private fetchGhLogin ( ) : Promise < string | null > {
3542+ this . ghLoginPromise ??= execGh ( [ "api" , "user" , "--jq" , ".login" ] , {
3543+ cwd : this . config . repositoryPath ,
3544+ timeoutMs : 10_000 ,
3545+ } )
3546+ . then ( ( res ) => {
3547+ const login = res . exitCode === 0 ? res . stdout . trim ( ) : "" ;
3548+ if ( ! login ) this . ghLoginPromise = null ;
3549+ return login || null ;
3550+ } )
3551+ . catch ( ( ) => {
3552+ this . ghLoginPromise = null ;
3553+ return null ;
3554+ } ) ;
3555+ return this . ghLoginPromise ;
3556+ }
3557+
35083558 private async cleanupSession ( {
35093559 completeEventStream = false ,
35103560 } : {
0 commit comments