From 51f08afef7ce901275a7d239c467c64fe9b4ff94 Mon Sep 17 00:00:00 2001 From: dhruv-15-03 <151501179+dhruv-15-03@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:18:38 +0530 Subject: [PATCH] Fix two crashes that make task_showcase unusable on Windows The bundled task showcase server returns HTTP 500 on every route when run on Windows. Two independent, unrelated causes: 1. strftime("%-m/%-d/%Y, %-I:%M:%S %p") raises ValueError The "%-" no-padding flag is a glibc extension. It is not part of the C standard and the Windows CRT rejects it outright, so /task/ raises ValueError: Invalid format string before rendering anything. Replaced with direct field formatting, which needs no platform-specific directives. Output is byte-identical to the glibc original, verified across midday/midnight boundaries: 2026-08-02 23:13:16 -> 8/2/2026, 11:13:16 PM 2026-01-01 00:00:00 -> 1/1/2026, 12:00:00 AM 2026-12-25 12:00:00 -> 12/25/2026, 12:00:00 PM 2026-10-09 09:05:07 -> 10/9/2026, 9:05:07 AM 2. Path.read_text() without an encoding Three call sites read JSON with no encoding argument, so Python uses the platform default: UTF-8 on Linux/macOS, but cp1252 on most Windows installs. Any non-ASCII character in task.json or report.json (a rupee sign, an em dash, a non-Latin company name) raises UnicodeDecodeError, and a BOM produces the confusing "Expecting value: line 1 column 1". Two other read_text() calls in the same module (build_steps, line 74 and 89) already pass encoding="utf-8". This aligns the remaining three with that existing convention. Reproduced on Windows 11 / Python 3.12.9 / Flask 3.1.3: both / and /task/ returned 500 before, and 200 after, rendering a report with 11 sections, 117 table rows and 105 cards. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- assets/task_showcase/app.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/assets/task_showcase/app.py b/assets/task_showcase/app.py index 81427d8e..dabef696 100644 --- a/assets/task_showcase/app.py +++ b/assets/task_showcase/app.py @@ -47,7 +47,7 @@ def list_tasks() -> list[dict]: info_path = d / "task.json" if not info_path.exists(): continue - info = json.loads(info_path.read_text()) + info = json.loads(info_path.read_text(encoding="utf-8")) info["short_id"] = d.name out.append(info) return out @@ -198,7 +198,7 @@ def task_view(short_id: str): info_path = task_dir / "task.json" if not info_path.exists(): abort(404) - info = json.loads(info_path.read_text()) + info = json.loads(info_path.read_text(encoding="utf-8")) info["short_id"] = short_id steps, final = build_steps(task_dir) # last-modified timestamp of the run for the "Updated" line @@ -206,13 +206,21 @@ def task_view(short_id: str): log_path = task_dir / "final_script_log.txt" if log_path.exists(): ts = _dt.datetime.fromtimestamp(log_path.stat().st_mtime) - updated = ts.strftime("%-m/%-d/%Y, %-I:%M:%S %p") + # Built by hand rather than with strftime: the "%-m" no-pad directive is a + # glibc extension and raises ValueError on Windows, where the equivalent is + # "%#m". Formatting the fields directly is portable everywhere. + hour12 = ts.hour % 12 or 12 + updated = ( + f"{ts.month}/{ts.day}/{ts.year}, " + f"{hour12}:{ts.minute:02d}:{ts.second:02d} " + f"{'AM' if ts.hour < 12 else 'PM'}" + ) else: updated = "" # Per-task structured report lives next to the run artifacts. report_path = task_dir / "report.json" if report_path.exists(): - report = json.loads(report_path.read_text()) + report = json.loads(report_path.read_text(encoding="utf-8")) else: report = {} sources = report.get("sources", [])