fix: keep response-file temp file for downstream steps

The temporary file created for response-file was being cleaned up
before downstream steps could access it. Now using keep: true to
ensure the file persists until the job completes.

Also added script/ to eslint ignores for the mock server.
This commit is contained in:
Sean Goedecke
2025-11-27 21:06:42 +00:00
parent fd73d0264c
commit 18d468666d
5 changed files with 16 additions and 62 deletions
+4 -26
View File
@@ -75,17 +75,13 @@ vi.mock('fs', () => ({
writeFileSync: mockWriteFileSync, writeFileSync: mockWriteFileSync,
})) }))
// Mocks for tmp module to control temporary file creation and cleanup // Mocks for tmp module to control temporary file creation
const mockRemoveCallback = vi.fn()
const mockFileSync = vi.fn().mockReturnValue({ const mockFileSync = vi.fn().mockReturnValue({
name: '/secure/temp/dir/modelResponse-abc123.txt', name: '/secure/temp/dir/modelResponse-abc123.txt',
removeCallback: mockRemoveCallback,
}) })
const mockSetGracefulCleanup = vi.fn()
vi.mock('tmp', () => ({ vi.mock('tmp', () => ({
fileSync: mockFileSync, fileSync: mockFileSync,
setGracefulCleanup: mockSetGracefulCleanup,
})) }))
// Mock MCP and inference modules // Mock MCP and inference modules
@@ -283,7 +279,7 @@ describe('main.ts', () => {
expect(mockProcessExit).toHaveBeenCalledWith(1) expect(mockProcessExit).toHaveBeenCalledWith(1)
}) })
it('creates secure temporary files with proper cleanup', async () => { it('creates temporary files that persist for downstream steps', async () => {
mockInputs({ mockInputs({
prompt: 'Test prompt', prompt: 'Test prompt',
'system-prompt': 'You are a test assistant.', 'system-prompt': 'You are a test assistant.',
@@ -291,34 +287,16 @@ describe('main.ts', () => {
await run() await run()
expect(mockSetGracefulCleanup).toHaveBeenCalledOnce() // Verify temp file is created with keep: true so it persists
expect(mockFileSync).toHaveBeenCalledWith({ expect(mockFileSync).toHaveBeenCalledWith({
prefix: 'modelResponse-', prefix: 'modelResponse-',
postfix: '.txt', postfix: '.txt',
keep: true,
}) })
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'response-file', '/secure/temp/dir/modelResponse-abc123.txt') expect(core.setOutput).toHaveBeenNthCalledWith(2, 'response-file', '/secure/temp/dir/modelResponse-abc123.txt')
expect(mockWriteFileSync).toHaveBeenCalledWith('/secure/temp/dir/modelResponse-abc123.txt', 'Hello, user!', 'utf-8') expect(mockWriteFileSync).toHaveBeenCalledWith('/secure/temp/dir/modelResponse-abc123.txt', 'Hello, user!', 'utf-8')
expect(mockRemoveCallback).toHaveBeenCalledOnce()
expect(mockProcessExit).toHaveBeenCalledWith(0) expect(mockProcessExit).toHaveBeenCalledWith(0)
}) })
it('handles cleanup errors gracefully', async () => {
mockRemoveCallback.mockImplementationOnce(() => {
throw new Error('Cleanup failed')
})
mockInputs({
prompt: 'Test prompt',
'system-prompt': 'You are a test assistant.',
})
await run()
expect(mockRemoveCallback).toHaveBeenCalledOnce()
expect(core.warning).toHaveBeenCalledWith('Failed to cleanup temporary file: Error: Cleanup failed')
expect(mockProcessExit).toHaveBeenCalledWith(0)
})
}) })
Generated Vendored
+5 -17
View File
@@ -52618,9 +52618,6 @@ function isPromptYamlFile(filePath) {
* @returns Resolves when the action is complete. * @returns Resolves when the action is complete.
*/ */
async function run() { async function run() {
let responseFile = null;
// Set up graceful cleanup for temporary files on process exit
tmpExports.setGracefulCleanup();
try { try {
const promptFilePath = coreExports.getInput('prompt-file'); const promptFilePath = coreExports.getInput('prompt-file');
const inputVariables = coreExports.getInput('input'); const inputVariables = coreExports.getInput('input');
@@ -52675,10 +52672,13 @@ async function run() {
modelResponse = await simpleInference(inferenceRequest); modelResponse = await simpleInference(inferenceRequest);
} }
coreExports.setOutput('response', modelResponse || ''); coreExports.setOutput('response', modelResponse || '');
// Create a secure temporary file instead of using the temp directory directly // Create a temporary file for the response that persists for downstream steps.
responseFile = tmpExports.fileSync({ // We use keep: true to prevent automatic cleanup - the file will be cleaned up
// by the runner when the job completes.
const responseFile = tmpExports.fileSync({
prefix: 'modelResponse-', prefix: 'modelResponse-',
postfix: '.txt', postfix: '.txt',
keep: true,
}); });
coreExports.setOutput('response-file', responseFile.name); coreExports.setOutput('response-file', responseFile.name);
if (modelResponse && modelResponse !== '') { if (modelResponse && modelResponse !== '') {
@@ -52695,18 +52695,6 @@ async function run() {
// Force exit to prevent hanging on open connections // Force exit to prevent hanging on open connections
process.exit(1); process.exit(1);
} }
finally {
// Explicit cleanup of temporary file if it was created
if (responseFile) {
try {
responseFile.removeCallback();
}
catch (cleanupError) {
// Log cleanup errors but don't fail the action
coreExports.warning(`Failed to cleanup temporary file: ${cleanupError}`);
}
}
}
// Force exit to prevent hanging on open connections // Force exit to prevent hanging on open connections
process.exit(0); process.exit(0);
} }
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -19,7 +19,7 @@ const compat = new FlatCompat({
export default [ export default [
{ {
ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules'], ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules', 'script/**'],
}, },
...compat.extends( ...compat.extends(
'eslint:recommended', 'eslint:recommended',
+5 -17
View File
@@ -18,11 +18,6 @@ import {
* @returns Resolves when the action is complete. * @returns Resolves when the action is complete.
*/ */
export async function run(): Promise<void> { export async function run(): Promise<void> {
let responseFile: tmp.FileResult | null = null
// Set up graceful cleanup for temporary files on process exit
tmp.setGracefulCleanup()
try { try {
const promptFilePath = core.getInput('prompt-file') const promptFilePath = core.getInput('prompt-file')
const inputVariables = core.getInput('input') const inputVariables = core.getInput('input')
@@ -101,10 +96,13 @@ export async function run(): Promise<void> {
core.setOutput('response', modelResponse || '') core.setOutput('response', modelResponse || '')
// Create a secure temporary file instead of using the temp directory directly // Create a temporary file for the response that persists for downstream steps.
responseFile = tmp.fileSync({ // We use keep: true to prevent automatic cleanup - the file will be cleaned up
// by the runner when the job completes.
const responseFile = tmp.fileSync({
prefix: 'modelResponse-', prefix: 'modelResponse-',
postfix: '.txt', postfix: '.txt',
keep: true,
}) })
core.setOutput('response-file', responseFile.name) core.setOutput('response-file', responseFile.name)
@@ -120,16 +118,6 @@ export async function run(): Promise<void> {
} }
// Force exit to prevent hanging on open connections // Force exit to prevent hanging on open connections
process.exit(1) process.exit(1)
} finally {
// Explicit cleanup of temporary file if it was created
if (responseFile) {
try {
responseFile.removeCallback()
} catch (cleanupError) {
// Log cleanup errors but don't fail the action
core.warning(`Failed to cleanup temporary file: ${cleanupError}`)
}
}
} }
// Force exit to prevent hanging on open connections // Force exit to prevent hanging on open connections