Skip to content

Commit f3d514e

Browse files
wenausclaude
andcommitted
epic: lift payload job report errors into the job record
Read the payload job report (config.Payload.jobreport) in update_job_data, store it as job metadata, and copy a nonzero exitCode/exitMsg into exeErrorCode/exeErrorDiag (diag truncated to the 500-character column). Missing or unparsable reports leave the job unchanged; the lift never raises. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157qGEPe3Q6W323qoZU96nd
1 parent c6942cc commit f3d514e

1 file changed

Lines changed: 44 additions & 0 deletions

File tree

pilot/user/epic/common.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"""Generic user specific functionality."""
2323

2424
import fnmatch
25+
import json
2526
import logging
2627
import os
2728
import re
@@ -203,6 +204,49 @@ def update_job_data(job: object) -> None:
203204
job: job object.
204205
"""
205206
validate_output_data(job)
207+
lift_payload_report(job)
208+
209+
210+
def lift_payload_report(job: JobData) -> None:
211+
"""Lift error information from the payload job report into the job object.
212+
213+
If the payload wrote a job report (config.Payload.jobreport) in the work directory, parse it and copy its
214+
error fields into the job object, so that the payload's own description of a failure is stored with the
215+
job record (exeErrorCode/exeErrorDiag) rather than only the pilot's interpretation of the wrapper output.
216+
The report is optional: a missing or unparsable file leaves the job object unchanged.
217+
218+
Expected report fields: exitCode (int), exitMsg (str).
219+
220+
Args:
221+
job: job object.
222+
"""
223+
path = os.path.join(job.workdir, config.Payload.jobreport)
224+
if not os.path.exists(path):
225+
logger.debug(f'no payload job report at {path}')
226+
return
227+
228+
try:
229+
with open(path, 'r', encoding='utf-8') as _fp:
230+
report = json.load(_fp)
231+
except (OSError, ValueError) as error:
232+
logger.warning(f'failed to read payload job report {path}: {error}')
233+
return
234+
235+
if not isinstance(report, dict):
236+
logger.warning(f'payload job report {path} does not contain a JSON object - ignoring it')
237+
return
238+
239+
job.metadata = report
240+
241+
exit_code = report.get('exitCode')
242+
exit_msg = report.get('exitMsg')
243+
if isinstance(exit_code, int) and exit_code != 0:
244+
job.exeerrorcode = exit_code
245+
if isinstance(exit_msg, str) and exit_msg:
246+
job.exeerrordiag = exit_msg[:500]
247+
logger.warning(f'payload job report: exitCode={exit_code} exitMsg={exit_msg}')
248+
elif isinstance(exit_code, int):
249+
logger.info('payload job report: exitCode=0')
206250

207251

208252
def validate_output_data(job: JobData) -> None:

0 commit comments

Comments
 (0)