Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/router/adapters/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,15 +331,16 @@ export class GitHubRouterAdapter implements RouterPlatformAdapter {
// Build the GitHub PR ack message with run link included before posting,
// so the actual comment on the PR contains the footer (not just internal metadata).
let githubAckMessage: string | undefined;
if (runLinksEnabled && event.workItemId) {
const workItemIdForLink = triggerResult?.workItemId ?? event.workItemId;
if (runLinksEnabled && workItemIdForLink) {
const dashboardUrl = getDashboardUrl();
if (dashboardUrl) {
const context = extractGitHubContext(payload, event.eventType);
const baseMessage = await generateAckMessage(agentType, context, project.id);
const link = buildWorkItemRunsLink({
dashboardUrl,
projectId: project.id,
workItemId: event.workItemId,
workItemId: workItemIdForLink,
});
githubAckMessage = link ? baseMessage + link : baseMessage;
}
Expand Down
39 changes: 39 additions & 0 deletions tests/unit/router/adapters/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ vi.mock('../../../../src/pm/trello/integration.js', () => ({
vi.mock('../../../../src/sentry.js', () => ({
captureException: vi.fn(),
}));
vi.mock('../../../../src/utils/runLink.js', () => ({
buildWorkItemRunsLink: vi.fn().mockReturnValue('\n\n🕵️ [View run](https://example.com)'),
getDashboardUrl: vi.fn().mockReturnValue('https://example.com'),
}));

import { isPMFocusedAgent } from '../../../../src/agents/definitions/loader.js';
import { findProjectByRepo } from '../../../../src/config/provider.js';
Expand All @@ -86,6 +90,7 @@ import { addEyesReactionToPR } from '../../../../src/router/pre-actions.js';
import type { GitHubJob } from '../../../../src/router/queue.js';
import { sendAcknowledgeReaction } from '../../../../src/router/reactions.js';
import type { TriggerRegistry } from '../../../../src/triggers/registry.js';
import { buildWorkItemRunsLink } from '../../../../src/utils/runLink.js';

const mockProject: RouterProjectConfig = {
id: 'p1',
Expand Down Expand Up @@ -407,6 +412,40 @@ describe('GitHubRouterAdapter', () => {
expect(postTrelloAck).toHaveBeenCalledWith('p1', 'trigger-card-id', expect.any(String));
});

it('uses triggerResult.workItemId over event.workItemId for GitHub PR run link', async () => {
// Ensure isPMFocusedAgent returns false so we take the GitHub PR ack path, not PM path
vi.mocked(isPMFocusedAgent).mockResolvedValue(false);
vi.mocked(loadProjectConfig).mockResolvedValue({
projects: [mockProject],
fullProjects: [{ id: 'p1', repo: 'owner/repo', runLinksEnabled: true } as never],
});
vi.mocked(resolveGitHubTokenForAckByAgent).mockResolvedValue({
token: 'ghp_test',
project: { id: 'p1' },
} as never);
vi.mocked(extractPRNumber).mockReturnValue(42);
vi.mocked(postGitHubAck).mockResolvedValue(1);

await adapter.postAck(
{
projectIdentifier: 'owner/repo',
eventType: 'pull_request',
workItemId: '1030', // PR number — should NOT be used for link
isCommentEvent: false,
// @ts-expect-error extended field
repoFullName: 'owner/repo',
},
{},
mockProject,
'review',
{ agentType: 'review', agentInput: {}, workItemId: 'trello-card-abc' },
);

expect(buildWorkItemRunsLink).toHaveBeenCalledWith(
expect.objectContaining({ workItemId: 'trello-card-abc' }),
);
});

it('returns undefined for PM-focused agents when no workItemId available', async () => {
vi.mocked(isPMFocusedAgent).mockResolvedValue(true);

Expand Down
Loading