--- title: Major Domo v2 Release — 2026.3.20 description: "Performance release: parallelized API calls, caching improvements, CI overhaul to tag-triggered releases, async hotfix, and chart path fix." type: reference domain: major-domo tags: [discord, major-domo, deployment, release-notes, docker, ci] --- # Major Domo v2 Release — 2026.3.20 **Date:** 2026-03-20 **Tags:** `2026.3.10`, `2026.3.11` (bugfix) **Image:** `manticorum67/major-domo-discordapp:production` **Server:** akamai (`/root/container-data/major-domo`) **Deploy method:** `.scripts/release.sh` → CI → `.scripts/deploy.sh` ## Release Summary Performance-focused release with 12 merged PRs covering parallelized API calls, caching improvements, CI workflow overhaul, and a production hotfix. Also retired the `next-release` staging branch in favor of direct-to-main merges with tag-triggered releases. ## Hotfix During Release **PR #117** — ScorecardTracker async mismatch. PR #106 added `await` to all `scorecard_tracker` method calls across `scorebug.py`, `live_scorebug_tracker.py`, and `cleanup_service.py`, but the tracker methods themselves were still synchronous. This caused `TypeError: object NoneType can't be used in 'await' expression` on `/scorebug` and `TypeError: object list can't be used in 'await' expression` in the background scorebug update loop. Fixed by making all 6 public `ScorecardTracker` methods async and adding 5 missing `await`s in `cleanup_service.py`. **Root cause:** PR #106 was created by an issue-worker agent that modified callers without modifying the tracker class. The async tracker conversion existed only in uncommitted working tree changes that were never included in any PR. **Lesson:** Issue-worker agent PRs that add `await` to calls must verify the called methods are actually async — not just that the callers compile. ## Infrastructure Changes ### CI: Tag-triggered releases (PRs #110, #113) Replaced branch-push CI with tag-push CI. Merging to `main` no longer triggers a Docker build. - **Before:** Push to `main` or `next-release` → auto-build → auto-tag CalVer - **After:** Push CalVer tag (`git tag 2026.3.11 && git push --tags`) → build → Docker image tagged `:version` + `:production` Also removed the `pull_request` trigger that was building Docker images on every PR branch push. ### Release and deploy scripts (PRs #114, #115) - `.scripts/release.sh` — auto-generates next CalVer tag, shows changelog, confirms, pushes tag - `.scripts/deploy.sh` — updated to use SSH alias (`ssh akamai`) and `:production` image tag ### Docker volume split (PR #86) Split the single `./storage:/app/data` volume into: - `./storage/major-domo-service-creds.json:/app/data/major-domo-service-creds.json:ro` (credentials) - `./storage:/app/storage:rw` (state files) Production compose on akamai was updated manually before deploy. All 5 tracker default paths changed from `data/` to `storage/`. ### Retired `next-release` branch All references to `next-release` removed from CLAUDE.md and CI workflow. New workflow: branch from `main` → PR to `main` → tag to release. ## Performance Changes ### Parallelized API calls | PR | What | Impact | |----|------|--------| | #88 | `schedule_service`: `get_team_schedule`, `get_recent_games`, `get_upcoming_games` use `asyncio.gather()` | Up to 18 sequential HTTP requests → concurrent | | #90 | Team lookups in `/publish-scorecard`, `/scorebug`, `/injury`, trade validation | 2 sequential calls → concurrent per location | | #102 | `asyncio.gather()` across multiple command files | Broad latency reduction | ### Caching | PR | What | Impact | |----|------|--------| | #99 | Cache user team lookup in `player_autocomplete` with 60s TTL, reduce Discord limit to 25 | Faster autocomplete on repeat use | | #98 | Replace Redis `KEYS` with `SCAN` for cache invalidation | Non-blocking invalidation | ### Micro-optimizations | PR | What | Impact | |----|------|--------| | #93 | Use `channel.purge()` instead of per-message `message.delete()` loops | 1 API call vs up to 100 per channel clear | | #96 | Replace `json.dumps(value)` probe with `isinstance()` in JSON logger | Eliminates full serialization on every log call | | #97 | Cache `inspect.signature()` at decoration time in all 3 decorators | Introspection cost paid once, not per-call | ## Cleanup | PR | What | |----|------| | #104 | Remove dead `@self.tree.interaction_check` decorator block and duplicate `self.maintenance_mode` assignment in `bot.py` | | #103 | Remove unused `weeks_ahead` parameter from `get_upcoming_games` | ## Test Coverage - 16 new tests for `schedule_service` (`test_services_schedule.py` — first coverage for this service) - Tests use existing `GameFactory`/`TeamFactory` from `tests/factories.py` - 2 existing scorebug tests updated for async tracker methods - Full suite: 967+ tests passing ## Bugfix: 2026.3.11 **PR #119** — `chart_service.py` CHARTS_FILE path still pointed to `data/charts.json` after PR #86 moved state files to `storage/`. The `/charts` autocomplete returned no results because the file was at `/app/storage/charts.json` but the code read from `/app/data/charts.json`. One-line path fix. **Root cause:** PR #86 updated the 5 tracker classes but missed `ChartService`, which uses a class-level `Path` constant instead of the `__init__` pattern used by the trackers. **Lesson:** When moving file paths across volumes, grep for the old path across the entire codebase — not just the files being modified. ## Deployment Notes - Production compose updated on akamai before deploy (volume split for PR #86) - Image tag changed from `:latest` to `:production` - Deployed three times total: 1. `2026.3.10` — initial release (broken `/scorebug` and scorebug tracker) 2. `2026.3.10` — re-tagged after hotfix #117 (async mismatch) 3. `2026.3.11` — chart path fix (#119) - Final deploy confirmed healthy — all background tasks started, gateway connected