Skip to content

Commit ea47f42

Browse files
exit with status code 1 if anything went wrong
1 parent 56253e3 commit ea47f42

2 files changed

Lines changed: 35 additions & 22 deletions

File tree

startriage/cli.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -358,23 +358,25 @@ def _outputcfg_from_args(args: argparse.Namespace, persistor: BugPersistor | Non
358358

359359
def main() -> None:
360360
try:
361-
asyncio.run(_run())
361+
had_errors = asyncio.run(_run())
362362
except KeyboardInterrupt:
363363
sys.exit(130)
364+
if had_errors:
365+
sys.exit(1)
364366

365367

366-
async def _run() -> None:
368+
async def _run() -> bool:
367369
parser = _build_parser()
368370
args = parser.parse_args()
369371

370372
log_setup(args.verbose - args.quiet)
371373

372374
config = load_config(args.config)
373375

374-
await args.func(args, config)
376+
return bool(await args.func(args, config))
375377

376378

377-
async def _run_triage(args: argparse.Namespace, config: StarTriageConfig) -> None:
379+
async def _run_triage(args: argparse.Namespace, config: StarTriageConfig) -> bool:
378380
provider = None
379381
output_cfg = _outputcfg_from_args(args)
380382
if args.ai is not None:
@@ -401,7 +403,7 @@ async def _run_triage(args: argparse.Namespace, config: StarTriageConfig) -> Non
401403
general = general.model_copy(update={"proposed_min_age": args.proposed_min_age})
402404
config.general = general
403405

404-
results = await run_triage(config, filter, output_cfg)
406+
results, had_errors = await run_triage(config, filter, output_cfg)
405407

406408
if args.ai is not None:
407409
from .ai import emit_ai_report, run_ai_over_triage_results
@@ -410,8 +412,10 @@ async def _run_triage(args: argparse.Namespace, config: StarTriageConfig) -> Non
410412
if report is not None:
411413
emit_ai_report(report, output_cfg.markdown_path)
412414

415+
return had_errors
413416

414-
async def _run_todo(args: argparse.Namespace, config: StarTriageConfig) -> None:
417+
418+
async def _run_todo(args: argparse.Namespace, config: StarTriageConfig) -> bool:
415419
if args.flag_recent is None and not args.subscribed:
416420
args.flag_recent = 6 # default flag-recent for todo mode
417421

@@ -426,36 +430,37 @@ async def _run_todo(args: argparse.Namespace, config: StarTriageConfig) -> None:
426430

427431
output_cfg = _outputcfg_from_args(args, BugPersistor(save_cfg))
428432

429-
await run_todo(
433+
return await run_todo(
430434
config,
431435
filter,
432436
output_cfg=output_cfg,
433437
subscribed=args.subscribed,
434438
)
435439

436440

437-
async def _run_analyze(args: argparse.Namespace, config: StarTriageConfig) -> None:
441+
async def _run_analyze(args: argparse.Namespace, config: StarTriageConfig) -> bool:
438442
if args.ai is None:
439443
from .ai import describe_bug_specs
440444

441445
report = await describe_bug_specs(args.bug)
442446
if report is None:
443447
print("No valid bugs found.", file=sys.stderr)
444-
return
448+
return True
445449
print(report)
446-
return
450+
return False
447451

448452
from .ai import build_provider, run_ai_over_bug_specs
449453

450454
provider = build_provider(config.ai, args.ai)
451455
report = await run_ai_over_bug_specs(config, args.bug, provider=provider)
452456
if report is None:
453457
print("No valid bugs to triage.", file=sys.stderr)
454-
return
458+
return True
455459
print(report)
460+
return False
456461

457462

458-
async def _set_config_settings(args: argparse.Namespace, _config: StarTriageConfig) -> None:
463+
async def _set_config_settings(args: argparse.Namespace, _config: StarTriageConfig) -> bool:
459464
updates: dict[str, dict] = {}
460465

461466
if args.default_team:
@@ -489,13 +494,14 @@ async def _set_config_settings(args: argparse.Namespace, _config: StarTriageConf
489494

490495
if not updates:
491496
print("No settings to update.")
492-
return
497+
return False
493498

494499
sensitive = "github_token" in updates.get("general", {}) or bool(
495500
{"github_token", "openrouter_api_key"} & updates.get("ai", {}).keys()
496501
)
497502
path = update_user_config(updates, config_path=args.config, sensitive=sensitive)
498503
print(f"Settings saved to {path!r}")
504+
return False
499505

500506

501507
async def _show_config(args: argparse.Namespace, config: StarTriageConfig) -> None:

startriage/triage.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,12 @@ async def run_triage(
4949
config: StarTriageConfig,
5050
opts: TaskFilterOptions,
5151
output_cfg: OutputConfig,
52-
) -> list[tuple[str, TriageResult]]:
52+
) -> tuple[list[tuple[str, TriageResult]], bool]:
5353
"""Daily triage: fetch all sources concurrently, print sections in order as they complete.
5454
55-
Returns the ``(source_name, result)`` pairs that were fetched successfully so
56-
callers (e.g. ``triage --ai``) can reuse them without re-fetching.
55+
Returns a tuple of the ``(source_name, result)`` pairs that were fetched
56+
successfully (so callers such as ``triage --ai`` can reuse them without
57+
re-fetching) and a ``had_errors`` flag indicating whether any source failed.
5758
"""
5859

5960
range = triage_task_note = ""
@@ -99,7 +100,7 @@ async def run_triage(
99100
for source in opts.sources:
100101
fetch_tasks[source.name] = asyncio.create_task(source.find(config, opts, FetchMode.triage))
101102

102-
results = await _output_results(output_cfg, fetch_tasks)
103+
results, had_errors = await _output_results(output_cfg, fetch_tasks)
103104

104105
# create markdown template
105106
if output_cfg.markdown_path:
@@ -126,20 +127,22 @@ async def run_triage(
126127

127128
logging.info("Markdown written to %s", output_cfg.markdown_path)
128129

129-
return results
130+
return results, had_errors
130131

131132

132133
async def run_todo(
133134
config: StarTriageConfig,
134135
filter: TaskFilterOptions,
135136
output_cfg: OutputConfig,
136137
subscribed: bool = False,
137-
) -> None:
138+
) -> bool:
138139
"""Todo / housekeeping triage: tag-filtered bugs, no date filter.
139140
140141
All sources in *filter.sources* are optional — pass a subset to fetch only
141142
that source. *subscribed* only controls LP fetch mode (subscription list
142143
vs. todo tag); GitHub is filtered by label regardless.
144+
145+
Returns ``had_errors``, ``True`` if any source failed to fetch.
143146
"""
144147
mode = FetchMode.subscribed if subscribed else FetchMode.todo
145148

@@ -150,17 +153,20 @@ async def run_todo(
150153
for source in filter.sources:
151154
fetch_tasks[source.name] = asyncio.create_task(source.find(config, filter, mode))
152155

156+
had_errors = False
153157
if output_cfg.bug_persistor is not None:
154-
results = await _output_results(output_cfg, fetch_tasks)
158+
results, had_errors = await _output_results(output_cfg, fetch_tasks)
155159
for _, result in results:
156160
await result.record(output_cfg.bug_persistor)
157161

158162
output_cfg.bug_persistor.save()
159163

164+
return had_errors
165+
160166

161167
async def _output_results(
162168
output_cfg: OutputConfig, fetch_tasks: dict[str, asyncio.Task[TriageResult]]
163-
) -> list[tuple[str, TriageResult]]:
169+
) -> tuple[list[tuple[str, TriageResult]], bool]:
164170
async with Spinner(set(fetch_tasks.keys())) as spinner:
165171
gathered: list[tuple[str, TriageResult | Exception]] = await asyncio.gather(
166172
*[_await_and_print(output_cfg, source, task, spinner) for source, task in fetch_tasks.items()]
@@ -172,7 +178,8 @@ async def _output_results(
172178
print(f"\nError fetching {source!r}:", file=sys.stderr)
173179
traceback.print_exception(exc, file=sys.stderr)
174180

175-
return [(s, r) for s, r in gathered if not isinstance(r, Exception)]
181+
results = [(s, r) for s, r in gathered if not isinstance(r, Exception)]
182+
return results, bool(errors)
176183

177184

178185
# Print sections in canonical order as each completes

0 commit comments

Comments
 (0)