From db268840d4265ced5d6f82dcd23fab9457686652 Mon Sep 17 00:00:00 2001 From: swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Sat, 10 Oct 2026 14:46:18 -0700 Subject: [PATCH] path, context: a refusal in --json mode keeps its prose (#1911) The front door asks every verb for --json. In that mode path and context run the answer inside redirect_stdout; a refusal (an endpoint nothing declares) leaves through die() -> SystemExit, which escaped the with-block, so neither the JSON nor its prose was printed: 'axiomcode path main NoSuchName' answered nothing at all, rc 2. SystemExit is caught inside the redirect and its code kept, so the document is printed with the refusal as its prose. Case a-refused-endpoint-is-said: the front-door check fails on the base (2/3), all pass after; control: a declared pair still answers. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> --- .../axiomcode/scripts/axiomcode-context | 4 +- .../skills/axiomcode/scripts/axiomcode-path | 6 ++- .../a-refused-endpoint-is-said/case.json | 45 +++++++++++++++++++ .../a-refused-endpoint-is-said/src/app.py | 6 +++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/cases/python/a-refused-endpoint-is-said/case.json create mode 100644 tests/cases/python/a-refused-endpoint-is-said/src/app.py diff --git a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-context b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-context index aabee9fc..9eb5ae2a 100755 --- a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-context +++ b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-context @@ -1648,7 +1648,9 @@ def cli(argv): sys.exit(code) import contextlib, io, json buf = io.StringIO() - with contextlib.redirect_stdout(buf): code = main(argv) + with contextlib.redirect_stdout(buf): + try: code = main(argv) + except SystemExit as e: code = e.code if isinstance(e.code, int) else (0 if e.code is None else 1) # a refusal keeps its prose RESULT['prose'] = buf.getvalue().rstrip('\n').split('\n') ax_evidence.annotate('context', RESULT, repo) print(json.dumps(RESULT, indent=2)) diff --git a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path index 741eaa4d..03a1e04e 100755 --- a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path +++ b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path @@ -2359,8 +2359,12 @@ if __name__ == '__main__': sys.exit(code) # the prose goes to a buffer, not to the caller: one code path produces both, and `prose` is # carried in the document so a machine answer is never LESS than the human one. + # a refusal (an endpoint nothing declares, a bad argument) leaves answer() through SystemExit: catch it inside + # the redirect, or the JSON and the prose it carries are both lost and the caller sees nothing at all buf = io.StringIO() - with contextlib.redirect_stdout(buf): code = answer() + with contextlib.redirect_stdout(buf): + try: code = answer() + except SystemExit as e: code = e.code if isinstance(e.code, int) else (0 if e.code is None else 1) RESULT['prose'] = buf.getvalue().rstrip('\n').split('\n') ax_evidence.annotate('path', RESULT, (RESULT.get('query') or {}).get('repo') or '.') print(json.dumps(RESULT, indent=2)) diff --git a/tests/cases/python/a-refused-endpoint-is-said/case.json b/tests/cases/python/a-refused-endpoint-is-said/case.json new file mode 100644 index 00000000..5c9b3461 --- /dev/null +++ b/tests/cases/python/a-refused-endpoint-is-said/case.json @@ -0,0 +1,45 @@ +{ + "lang": "python", + "src": "src", + "checks": [ + { + "why": "path with an endpoint nothing declares says so; the refusal reached the caller as nothing at all once the front door asked for --json", + "run": [ + "path", + "main", + "NoSuchName" + ], + "expect_error": true, + "want": [ + "nothing named 'NoSuchName'" + ], + "env": { + "AXIOMCODE_FRONT": "1" + } + }, + { + "why": "the same refusal from the raw verb", + "run": [ + "path", + "main", + "NoSuchName" + ], + "expect_error": true, + "want": [ + "nothing named 'NoSuchName'" + ] + }, + { + "why": "control: a path between two declared functions still answers", + "run": [ + "path", + "main", + "helper" + ], + "want": [ + "main \u2192 helper", + "\u2192 [known_edge" + ] + } + ] +} diff --git a/tests/cases/python/a-refused-endpoint-is-said/src/app.py b/tests/cases/python/a-refused-endpoint-is-said/src/app.py new file mode 100644 index 00000000..36024cc8 --- /dev/null +++ b/tests/cases/python/a-refused-endpoint-is-said/src/app.py @@ -0,0 +1,6 @@ +def helper(): + return 1 + + +def main(): + return helper()