fix(workflows): render gate show_file contents in the interactive prompt#2810
fix(workflows): render gate show_file contents in the interactive prompt#2810doquanghuy wants to merge 4 commits into
Conversation
b43191b to
5412c47
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes the built-in workflow gate step so that a configured show_file is actually rendered in the interactive prompt, allowing operators to review the referenced file contents before choosing approve/reject. It adds a bounded, non-throwing file-read helper and extends test coverage around the interactive/non-interactive behaviors.
Changes:
- Render
show_filecontents inside the interactive gate prompt (before the options). - Add
_read_show_file()helper that caps output length and degrades to a one-line notice on read/decode errors. - Add targeted tests covering interactive rendering, missing files, non-interactive pause behavior, empty files, and truncation.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/workflows/steps/gate/__init__.py |
Passes show_file into the gate prompt and safely renders file contents with truncation/error notices. |
tests/test_workflows.py |
Adds regression tests for interactive rendering and safe behavior across missing/empty/large files and non-interactive runs. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 1
| if show_file and isinstance(show_file, str): | ||
| print(" │") | ||
| print(f" │ {show_file}:") | ||
| for line in GateStep._read_show_file(show_file): | ||
| print(f" │ {line}") |
There was a problem hiding this comment.
Addressed: show_file is now coerced with str() after template evaluation in execute(), so a single-expression template (or literal) that resolves to a non-string is rendered rather than skipped. Covered by test_templated_show_file_resolving_to_non_string_is_coerced.
The gate step read and recorded `show_file` but never displayed its contents at the interactive prompt, so the operator approved/rejected without seeing the referenced file. Render the file inside the prompt when stdin is a TTY, with a graceful notice for missing/unreadable files. Non-interactive PAUSED behaviour, exit codes, resume semantics, and no-`show_file` output are unchanged. Closes github#2809. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5412c47 to
bf2f25c
Compare
|
Thanks @copilot — fixed in the latest push.
@mnriem ready for another look when you have a moment. |
| try: | ||
| with Path(show_file).open(encoding="utf-8") as handle: | ||
| for line in handle: | ||
| if len(lines) >= GateStep.MAX_SHOW_FILE_LINES: | ||
| truncated = True | ||
| break | ||
| lines.append(line.rstrip("\n")) | ||
| except (OSError, UnicodeDecodeError) as exc: | ||
| return [f"(could not read file: {exc})"] |
There was a problem hiding this comment.
Addressed in 017e84d: _read_show_file now catches ValueError as well (the error Path.open raises for an embedded NUL byte, before any I/O), so such a path degrades to the (could not read file: …) notice instead of crashing. Covered by test_read_show_file_invalid_path_does_not_raise.
…le reads The gate prompt rendered show_file by passing it as a third positional argument to _prompt. A test that stubs _prompt with a two-argument lambda (test_gate_abort_still_halts_with_continue_on_error) then failed once the branch caught up to main, because the call site passed three arguments to the two-argument stub. Compose the show_file material into the displayed message in execute() and keep _prompt to its (message, options) contract. Display data no longer widens the interactive seam, so stubbing _prompt stays stable and future review material can be added without breaking callers. _prompt now renders a multi-line message inside the gate box. Also catch ValueError in _read_show_file so a path the OS rejects outright (e.g. an embedded NUL byte) degrades to a notice instead of crashing the prompt, matching the helper's stated contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pushed a fix that also brings the branch up to date with CI failure (the merge-time Rather than patch the stub, I kept Copilot's open note. Local: full @mnriem ready for another look when you have a moment. |
The multi-line render loop split the message on newlines, which assumes a str. A non-string message (e.g. a YAML numeric literal) previously rendered fine through the old f-string and would now raise on .split. Coerce with str() to preserve that tolerance, and add a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Description
Closes #2809.
The built-in
gatestep accepts ashow_fileconfig field. The value was read, template-expanded, and stored inoutput["show_file"], but its contents were never displayed at the interactive prompt — the operator was asked to approve/reject without seeing the referenced file.This renders
show_fileinside the interactive gate prompt, before the options. A missing or undecodable file degrades to a short one-line notice instead of raising, so a misconfigured path never breaks the prompt.What changed
GateStep._prompt(...)now acceptsshow_fileand prints its contents (each line within the gate box) before the options.GateStep._read_show_file(...)helper reads the file as UTF-8 and returns a(could not read file: …)/(file is empty)notice on error/empty instead of raising.Compatibility
PAUSED) path is unchanged — the file is not read when stdin is not a TTY.on_rejecthandling, and resume semantics are unchanged.show_fileproduce byte-identical output to before.Testing
uv run specify --helpuv sync --extra test && uv run pytest— 3310 passed, 40 skippedAdded three targeted tests in
tests/test_workflows.py::TestGateStep:show_filecontents and returns the chosen option;show_fileshows the notice and does not crash;output["show_file"]without reading the file.AI Disclosure
Used Claude to trace the bug in
gate/__init__.py, draft the fix and tests, and write this PR body. The behaviour was reproduced and the diff reviewed locally before submission.