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
17 changes: 16 additions & 1 deletion src/agents/shared/runTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
completeRun,
createRun,
storeRunLogs,
updateRunJobId,
} from '../../db/repositories/runsRepository.js';
import { logger } from '../../utils/logging.js';
import type { FileLogger } from './executionPipeline.js';
Expand All @@ -28,14 +29,15 @@ export interface RunTrackingInput {

/**
* Create a DB run record, suppressing errors so agent execution is unaffected.
* If JOB_ID env var is set (Docker mode), store it immediately after run creation.
*/
export async function tryCreateRun(
input: RunTrackingInput,
model?: string,
maxIterations?: number,
): Promise<string | undefined> {
try {
return await createRun({
const runId = await createRun({
projectId: input.projectId,
workItemId: input.workItemId,
prNumber: input.prNumber,
Expand All @@ -45,6 +47,19 @@ export async function tryCreateRun(
model,
maxIterations,
});

// Store BullMQ jobId if running in Docker (JOB_ID env var is set)
const jobId = process.env.JOB_ID;
if (jobId) {
try {
await updateRunJobId(runId, jobId);
} catch (err) {
logger.warn('Failed to store job ID for run', { runId, jobId, error: String(err) });
// Continue - failure to store jobId should not block agent execution
}
}

return runId;
} catch (err) {
logger.warn('Failed to create run record', { error: String(err) });
return undefined;
Expand Down
49 changes: 48 additions & 1 deletion tests/unit/agents/shared/runTracking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ vi.mock('../../../../src/db/repositories/runsRepository.js', () => ({
createRun: vi.fn(),
completeRun: vi.fn(),
storeRunLogs: vi.fn(),
updateRunJobId: vi.fn(),
}));

vi.mock('../../../../src/utils/logging.js', () => ({
Expand Down Expand Up @@ -35,12 +36,14 @@ import {
completeRun,
createRun,
storeRunLogs,
updateRunJobId,
} from '../../../../src/db/repositories/runsRepository.js';
import { logger } from '../../../../src/utils/logging.js';

const mockCreateRun = vi.mocked(createRun);
const mockCompleteRun = vi.mocked(completeRun);
const mockStoreRunLogs = vi.mocked(storeRunLogs);
const mockUpdateRunJobId = vi.mocked(updateRunJobId);
const mockExistsSync = vi.mocked(fs.existsSync);
const mockReadFileSync = vi.mocked(fs.readFileSync);

Expand All @@ -63,9 +66,18 @@ const baseInput: RunTrackingInput = {
};

describe('tryCreateRun', () => {
it('creates a run and returns the run ID', async () => {
beforeEach(() => {
mockCreateRun.mockResolvedValue('run-abc');
mockUpdateRunJobId.mockResolvedValue(undefined);
mockUpdateRunJobId.mockClear();
});

afterEach(() => {
// biome-ignore lint/performance/noDelete: Clean up test environment
delete process.env.JOB_ID;
});

it('creates a run and returns the run ID', async () => {
const runId = await tryCreateRun(baseInput, 'claude-3-5-sonnet-20241022', 25);
expect(runId).toBe('run-abc');
expect(mockCreateRun).toHaveBeenCalledWith({
Expand All @@ -80,6 +92,41 @@ describe('tryCreateRun', () => {
});
});

it('stores the job ID when JOB_ID env var is set', async () => {
process.env.JOB_ID = 'job-12345';
mockUpdateRunJobId.mockClear();

const runId = await tryCreateRun(baseInput);
expect(runId).toBe('run-abc');
expect(mockUpdateRunJobId).toHaveBeenCalledWith('run-abc', 'job-12345');
});

it('does not call updateRunJobId when JOB_ID env var is not set', async () => {
// biome-ignore lint/performance/noDelete: Clean environment before test
delete process.env.JOB_ID;
mockUpdateRunJobId.mockClear();

const runId = await tryCreateRun(baseInput);
expect(runId).toBe('run-abc');
expect(mockUpdateRunJobId).not.toHaveBeenCalled();
});

it('logs a warning but continues when updateRunJobId fails', async () => {
process.env.JOB_ID = 'job-12345';
mockUpdateRunJobId.mockRejectedValue(new Error('Failed to update job ID'));

const runId = await tryCreateRun(baseInput);
expect(runId).toBe('run-abc');
expect(logger.warn).toHaveBeenCalledWith(
'Failed to store job ID for run',
expect.objectContaining({
runId: 'run-abc',
jobId: 'job-12345',
error: expect.stringContaining('Failed to update job ID'),
}),
);
});

it('returns undefined and logs a warning when createRun throws', async () => {
mockCreateRun.mockRejectedValue(new Error('DB error'));

Expand Down
Loading