---
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
