Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
5 changes: 3 additions & 2 deletions dailybot_cli/commands/checkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
emit_json,
enforce_plan_access,
exit_for_api_error,
normalize_checkin_entity_json,
require_auth,
validate_user_filter,
)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions dailybot_cli/commands/public_api_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment on lines +533 to +536

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing coverage for the non-trivial branches of this helper (and two of three call sites).

The only new test exercises checkin create --json when the payload has id and no uuid. That leaves untested:

  1. Preserve explicit uuid — the docstring promises never to overwrite; a regression that always copies iduuid would still pass the create test.
  2. followup_uuid-only payloads — the fallback used when id is absent.
  3. checkin config --json / checkin show --json — both call this helper in this PR, but neither asserts uuid (the existing show JSON test still only checks nested question fields).

Suggested additions (unit or CliRunner):

assert normalize_checkin_entity_json({"id": "a", "uuid": "b"})["uuid"] == "b"
assert normalize_checkin_entity_json({"followup_uuid": "fu"})["uuid"] == "fu"

plus a show/config --json assertion that payload["uuid"] is present when the API returns only id.

return enriched


def find_pending_checkin(
pending_checkins: list[dict[str, Any]],
followup_uuid: str,
Expand Down
3 changes: 2 additions & 1 deletion dailybot_cli/commands/user_scoped_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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)

Expand Down
6 changes: 4 additions & 2 deletions dailybot_cli/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"]
Comment on lines +1465 to +1468

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_checkin_uuid already exists a few hundred lines above with a different key order.

This block inlines uuid → id → followup_uuid, while _checkin_uuid (dailybot_cli/display.py ~813) uses uuid → followup_uuid → id. For create/config payloads that only have id the result matches; if a payload ever carries both id and followup_uuid with different values, create output and status tables would disagree.

Prefer reusing the helper so authoring and status stay consistent:

Suggested change
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}"]
checkin_uuid: str = _checkin_uuid(checkin)
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")
Expand Down
16 changes: 16 additions & 0 deletions docs/RELEASE_AND_DISTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions tests/authoring_helpers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
29 changes: 29 additions & 0 deletions tests/checkin_authoring_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading