From 6668665a2a72baf6c7c38c13ef0afe392c5dd862 Mon Sep 17 00:00:00 2001 From: Sergio Alexander Florez Galeano Date: Sun, 30 Aug 2026 16:15:49 +0000 Subject: [PATCH] fix(release): pin PSR past GitPython 3.1.60 Actor.name_email_regex break MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto Release for the labels merge failed because GitPython 3.1.60 removed Actor.name_email_regex and python-semantic-release ≤10.6.1 crashed on config load — no tag, no PyPI publish past v3.8.0. Pin PSR ≥10.6.2 and exclude the broken GitPython wheel. Also expose uuid on checkin create/ config/show --json (API returns id) so label-assign scripts stay consistent. Co-authored-by: Cursor --- .github/workflows/auto-release.yml | 10 ++++++- dailybot_cli/commands/checkin.py | 5 ++-- dailybot_cli/commands/public_api_helpers.py | 17 ++++++++++++ dailybot_cli/commands/user_scoped_actions.py | 3 +- dailybot_cli/display.py | 6 ++-- docs/RELEASE_AND_DISTRIBUTION.md | 16 +++++++++++ tests/authoring_helpers_test.py | 1 + tests/checkin_authoring_test.py | 29 ++++++++++++++++++++ 8 files changed, 81 insertions(+), 6 deletions(-) diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index f5cc93d..398b376 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -155,7 +155,15 @@ jobs: - name: Install python-semantic-release if: steps.skip_check.outputs.skip != 'true' - run: pip install "python-semantic-release>=10,<11" + # Pin PSR >=10.6.2: GitPython 3.1.60 removed Actor.name_email_regex and + # every `semantic-release` config load crashed with + # AttributeError: type object 'Actor' has no attribute 'name_email_regex' + # (see python-semantic-release#1476). That broke the labels PR auto-release + # on 2026-08-27 (run 33115288463) so v3.8.0 never advanced and PyPI was + # never published. 10.6.2 validates commit_author internally. Also exclude + # the broken GitPython wheel as belt-and-suspenders in case a future PSR + # pin regresses. + run: pip install "python-semantic-release>=10.6.2,<11" "GitPython!=3.1.60" - name: Capture latest tag (before) if: steps.skip_check.outputs.skip != 'true' diff --git a/dailybot_cli/commands/checkin.py b/dailybot_cli/commands/checkin.py index b777b9e..c701dd6 100644 --- a/dailybot_cli/commands/checkin.py +++ b/dailybot_cli/commands/checkin.py @@ -30,6 +30,7 @@ emit_json, enforce_plan_access, exit_for_api_error, + normalize_checkin_entity_json, require_auth, validate_user_filter, ) @@ -524,7 +525,7 @@ def checkin_create( exit_for_api_error(exc, json_mode) if json_mode: - emit_json(result) + emit_json(normalize_checkin_entity_json(result)) return print_checkin_created(result) @@ -610,7 +611,7 @@ def checkin_config( exit_for_api_error(exc, json_mode) if json_mode: - emit_json(result) + emit_json(normalize_checkin_entity_json(result)) return print_success(f"Check-in {followup_uuid} updated.") print_checkin_created(result, updated=True) diff --git a/dailybot_cli/commands/public_api_helpers.py b/dailybot_cli/commands/public_api_helpers.py index d2cea8d..74ee494 100644 --- a/dailybot_cli/commands/public_api_helpers.py +++ b/dailybot_cli/commands/public_api_helpers.py @@ -520,6 +520,23 @@ def normalize_checkin_list_json(data: dict[str, Any]) -> dict[str, Any]: return {"pending_checkins": pending, "count": data.get("count", len(pending))} +def normalize_checkin_entity_json(checkin: dict[str, Any]) -> dict[str, Any]: + """Ensure authoring check-in payloads expose a stable ``uuid`` field. + + Create/config responses from ``/v1/checkins/`` historically return the + follow-up id under ``id`` (not ``uuid``). Forms and Labels use ``uuid`` + everywhere, so scripting ``checkin create --json`` → ``label assign`` + was awkward. Copy ``id`` into ``uuid`` when missing; never overwrite an + explicit ``uuid``. + """ + enriched: dict[str, Any] = dict(checkin) + if not enriched.get("uuid"): + legacy_id: Any = enriched.get("id") or enriched.get("followup_uuid") + if legacy_id: + enriched["uuid"] = str(legacy_id) + return enriched + + def find_pending_checkin( pending_checkins: list[dict[str, Any]], followup_uuid: str, diff --git a/dailybot_cli/commands/user_scoped_actions.py b/dailybot_cli/commands/user_scoped_actions.py index e54838f..e149d62 100644 --- a/dailybot_cli/commands/user_scoped_actions.py +++ b/dailybot_cli/commands/user_scoped_actions.py @@ -16,6 +16,7 @@ exit_for_api_error, find_pending_checkin, get_current_user_uuid, + normalize_checkin_entity_json, normalize_checkin_list_json, parse_answer_flags, ) @@ -671,7 +672,7 @@ def execute_checkin_show( exit_for_api_error(exc, json_mode) if json_mode: - emit_json(detail) + emit_json(normalize_checkin_entity_json(detail)) return print_checkin_detail(detail) diff --git a/dailybot_cli/display.py b/dailybot_cli/display.py index a9b1553..9a31b66 100644 --- a/dailybot_cli/display.py +++ b/dailybot_cli/display.py @@ -1462,8 +1462,10 @@ def print_form_created(form: dict[str, Any], *, updated: bool = False) -> None: def print_checkin_created(checkin: dict[str, Any], *, updated: bool = False) -> None: """Display a created (or, with ``updated=True``, edited) check-in + summary.""" name: str = str(checkin.get("name") or "") - checkin_id: str = str(checkin.get("id") or checkin.get("uuid") or "") - lines: list[str] = [f"[bold]{name}[/bold]", f"ID: {checkin_id}"] + checkin_uuid: str = str( + checkin.get("uuid") or checkin.get("id") or checkin.get("followup_uuid") or "" + ) + lines: list[str] = [f"[bold]{name}[/bold]", f"UUID: {checkin_uuid}"] schedule: dict[str, Any] = checkin.get("schedule") or {} if schedule: days: Any = schedule.get("days") diff --git a/docs/RELEASE_AND_DISTRIBUTION.md b/docs/RELEASE_AND_DISTRIBUTION.md index c647d48..9e0fc91 100644 --- a/docs/RELEASE_AND_DISTRIBUTION.md +++ b/docs/RELEASE_AND_DISTRIBUTION.md @@ -148,6 +148,22 @@ If `auto-release.yml` was skipped (e.g. CI was down at merge time), you can re-r If you need a release for commits that don't qualify (e.g. an emergency `chore`-only release), fall back to the tag-triggered flow below. +#### Stuck release: `Actor.name_email_regex` / GitPython 3.1.60 + +On 2026-08-27 the labels merge (PR #78) failed Auto Release with: + +```text +AttributeError: type object 'Actor' has no attribute 'name_email_regex' +``` + +GitPython **3.1.60** removed that attribute; `python-semantic-release` ≤10.6.1 read it on every config load ([python-semantic-release#1476](https://github.com/python-semantic-release/python-semantic-release/issues/1476)). No `v*` tag was cut, so PyPI stayed on `v3.8.0`. + +`auto-release.yml` now pins `python-semantic-release>=10.6.2,<11` (validates `commit_author` without that attribute) and `GitPython!=3.1.60`. To cut the missed release after the pin is on `main`: + +```bash +gh workflow run auto-release.yml --ref main +``` + ### Opt-in release skip — the `[skip release]` marker > Every PR releases by default. This is the **only** way to suppress it. diff --git a/tests/authoring_helpers_test.py b/tests/authoring_helpers_test.py index fb990ee..c5c96f8 100644 --- a/tests/authoring_helpers_test.py +++ b/tests/authoring_helpers_test.py @@ -697,6 +697,7 @@ def test_checkin_created(self) -> None: output: str = capture.get() assert "Standup" in output assert "09:00" in output + assert "UUID: fu-1" in output def test_questions_table_empty(self) -> None: with display.console.capture() as capture: diff --git a/tests/checkin_authoring_test.py b/tests/checkin_authoring_test.py index d9e58f1..f1cfa2d 100644 --- a/tests/checkin_authoring_test.py +++ b/tests/checkin_authoring_test.py @@ -43,6 +43,35 @@ def _client() -> Any: class TestCheckinCreate: + def test_create_json_exposes_uuid_alias(self, runner: CliRunner, qfile: str) -> None: + """API returns ``id``; --json must also expose ``uuid`` for label assign scripts.""" + with _auth(), _client() as cls: + client: MagicMock = cls.return_value + client.create_checkin.return_value = CHECKIN_PAYLOAD + client.list_teams.return_value = [{"uuid": "t-1", "name": "Eng"}] + result = runner.invoke( + cli, + [ + "checkin", + "create", + "-n", + "Standup", + "--time", + "09:00", + "--days", + "1,2,3,4,5", + "--team", + "Eng", + "--questions-file", + qfile, + "--json", + ], + ) + assert result.exit_code == 0, result.output + payload: dict[str, Any] = json.loads(result.output) + assert payload["id"] == "fu-1" + assert payload["uuid"] == "fu-1" + def test_create_with_schedule(self, runner: CliRunner, qfile: str) -> None: with _auth(), _client() as cls: client: MagicMock = cls.return_value