Add --no-deps to nginx and admin docker compose commands to prevent
them from cascading to auth. The auth container has secrets (like
TWITCH_CLIENT_ID) that are only available in auth_build.yml, not in
docker_build.yml. Without --no-deps, docker compose would recreate
auth with blank env vars when it detected config changes.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Change version scheme from git describe (which picked up unrelated tags
like busybox-1.35.0) to commit-count based versions like 1.0.9548.
This gives automatic, always-incrementing version numbers that Sparkle
can properly compare for updates.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Use ditto instead of cp -R to copy Sparkle.framework (preserves bundle structure)
- Skip individual signing of Sparkle's internal XPC services, apps, and executables
- Use --deep flag when signing Sparkle.framework to handle its internal components
- Verify cached Sparkle.framework has proper symlink structure, re-download if corrupted
Fixes "bundle format unrecognized" error during codesigning.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add native Sparkle plugin to enable Mac auto-updates
The Sparkle framework was being injected into the app bundle, but
nothing was initializing it. This adds:
- Native Objective-C plugin (SparklePlugin.m) that initializes
SPUStandardUpdaterController at runtime
- C# wrapper (SparkleUpdater.cs) for Unity to call the native plugin
- SparkleInitializer.cs uses RuntimeInitializeOnLoadMethod to
automatically initialize Sparkle at app startup
- Build script to compile the plugin as a universal binary
The plugin is weak-linked against Sparkle.framework, which is injected
separately by inject_sparkle.sh during the build process.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Convert Sparkle plugin build from clang to Bazel
- Add Sparkle framework as http_archive dependency in MODULE.bazel
- Add BUILD.sparkle to import the framework
- Add BUILD.bazel for SparklePlugin using macos_bundle rule
- Update build_sparkle_plugin.sh to use Bazel instead of direct clang
- Register Apple CC toolchain extension
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix symbol exports for SparklePlugin native library
The C functions need to be exported with visibility("default") and
explicit linker flags for Unity P/Invoke to find them. Without this,
the bundle binary had no exported symbols.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Re-enable Sparkle auto-update integration for Mac builds
Restores Sparkle integration that was temporarily removed in #5317:
- Restore inject_sparkle.sh script
- Add Sparkle injection step to mac_build.yml
- Re-enable Sparkle signing and appcast updates in deploy step
Combined with native SparklePlugin that initializes Sparkle at runtime.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Build SparklePlugin.bundle before Unity build
The SparklePlugin.bundle.meta file tells Unity to include the plugin,
but the actual bundle needs to be built by Bazel first.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Convert SparklePlugin Info.plist to XML format
Unity's build system requires Info.plist files in XML format,
but Bazel outputs them in binary plist format.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Remove Sparkle auto-update integration from Mac builds
Temporarily removing Sparkle integration to get Mac builds working:
- Remove inject_sparkle.sh script and workflow step
- Make mac_build_handler's Sparkle private key optional
- Skip Sparkle signing and appcast updates when no key provided
This allows Mac builds to complete without Sparkle. Auto-updates can
be re-enabled later once the basic build pipeline is stable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Use clean checkout to remove stale SparklePlugin.bundle
The runner had a leftover SparklePlugin.bundle from previous builds
which was causing Unity to fail when trying to process it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Eagle server already persists to S3 on every game save via CompoundPersister
when S3Credentials.isEnabled. The deploy script's s3cmd backup was redundant
and caused warnings when s3cmd wasn't installed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add dedicated self-hosted runners for parallel Unity builds:
- unity-mac: Mac Unity builds and deployment
- unity-windows: Windows Unity builds (cross-compiled on Mac)
- notarize: Notarization waiting (lightweight, doesn't block builds)
Split mac_build.yml into 3 jobs:
1. build-and-sign (unity-mac): Build, sign, submit to Apple
2. wait-notarization (notarize): Wait for Apple, staple ticket
3. deploy (unity-mac): Deploy notarized app
This allows:
- Mac and Windows Unity builds to run in parallel
- Notarization waiting doesn't block other builds
- All runners share the same Mac Mini hardware
New scripts:
- notarize_submit.sh: Submit without waiting, output submission ID
- notarize_wait.sh: Wait for submission ID, staple ticket
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Use rm -f instead of rm when cleaning up the zip file after notarization.
The zip may already be deleted if a previous step failed and was retried,
causing the script to fail even when notarization actually succeeded.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Auto-invalidate game cache when flush marker is updated
During warmup, the staging server may cache stale game data that was
loaded before the active server flushed. Instead of exposing an RPC
for cache invalidation (which leaks internal state), the server now
automatically detects when the flush marker is updated and invalidates
any cached games.
Changes:
- GamesManager: Added `invalidateCacheIfFlushMarkerUpdated()` that
checks the flush marker's modification time and clears the cache
if it's been updated since the last check
- Called before checking if a game is in cache, so stale data is
cleared before any attempt to use it
- Removed the InvalidateGameCache RPC (no longer needed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix user wait window metric to measure actual user impact
The max user wait window was measuring from when nginx switching
started (before recreation) instead of when it completed. This
inflated the metric by ~12s because nginx recreation time was included.
Users can only experience a wait AFTER nginx starts routing to the
staging server, so we now measure from nginx_switch_end (after
recreation completes) to flush_end.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
During warmup, the staging server may cache stale game data that was
loaded before the active server flushed. Instead of exposing an RPC
for cache invalidation (which leaks internal state), the server now
automatically detects when the flush marker is updated and invalidates
any cached games.
Changes:
- GamesManager: Added `invalidateCacheIfFlushMarkerUpdated()` that
checks the flush marker's modification time and clears the cache
if it's been updated since the last check
- Called before checking if a game is in cache, so stale data is
cleared before any attempt to use it
- Removed the InvalidateGameCache RPC (no longer needed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The saves directory is owned by root (created by Docker) but the deploy
script runs as the deploy user. Use docker exec to create marker files
from inside a running container that has the saves directory mounted.
- create_deployment_marker: uses active container (running before staging starts)
- create_flush_marker: uses staging container (running after active stops)
- cleanup_markers_on_failure: uses active container (still running on failure)
- remove_stale_deployment_marker: finds any running eagle container
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix nginx not picking up config changes during blue-green deploy
Root cause: `docker compose restart nginx` doesn't refresh bind-mounted
volume files. The nginx container keeps using its cached copy of
nginx.conf even after we update the host file with sed.
This caused nginx to keep trying to connect to the old (now deleted)
eagle instance, resulting in 502 errors after deployment.
Fix:
- Use `docker compose up -d --force-recreate nginx` instead of `restart`
This recreates the container, forcing it to read the updated config
- Add verification that nginx picked up the correct backend
- Remove the useless pre-validation (it validated old config in old container)
The progression of failed fixes:
1. `nginx -s reload` - doesn't re-resolve Docker DNS
2. `docker compose restart` - doesn't refresh bind-mounted files
3. `docker compose up -d --force-recreate` - THIS WORKS
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Reorder deploy: switch nginx BEFORE stopping blue to avoid 502s
Previous order (caused 502 errors):
1. Stop blue → nginx still points to blue → 502!
2. Update nginx config
3. Recreate nginx → traffic finally works
New order (eliminates 502 window):
1. Update nginx config
2. Recreate nginx → traffic goes to green (blue still running)
3. Stop blue → flushes state to disk
4. 3-second pause for flush to complete
The stale data race condition is minimized by stopping blue immediately
after the nginx switch. Users reconnecting to green will lazy-load
fresh game data from disk (after blue has flushed).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add flush marker coordination for zero-downtime blue-green deploys
This ensures green never serves stale game data during deployments:
1. Deploy script creates .deployment_in_progress marker at start
2. Green's lazy-load waits for flush marker if deployment in progress
3. nginx switches to green BEFORE stopping blue (zero 502 downtime)
4. Blue stops, flushes state to disk
5. Deploy script creates .flush_complete marker
6. Green's waiting lazy-loads proceed with fresh disk data
Key changes:
- GamesManager.scala: Add waitForFlushMarker() that blocks lazy-load
during deployment until flush marker appears (30s timeout)
- GamesManager.scala: Auto-clean stale markers >5 minutes old
- GamesManager.scala: Add deployment ID correlation in logs [DEPLOY:xxx]
- GamesManager.scala: Report flush marker timeouts to Sentry
- deploy-blue-green.sh: Reorder to switch nginx BEFORE stopping blue
- deploy-blue-green.sh: Add marker file coordination with deployment ID
- deploy-blue-green.sh: Add timing metrics (flush duration, user wait window)
- nginx.conf: Keep variable-based routing (Docker DNS only resolves
running containers, so upstream+backup doesn't work)
Monitoring and observability:
- All deployment-related logs tagged with [DEPLOY:timestamp] for correlation
- Wait duration logged for each lazy-load during deployment
- Flush marker timeouts reported to Sentry for alerting
- Deploy script logs total duration, flush duration, max user wait window
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
nginx reload doesn't always force DNS re-resolution in Docker. During
blue-green deployment, after updating nginx.conf to point to the new
instance (e.g., eagle-green:40032), nginx -s reload would sometimes
keep trying to connect to the old (now removed) container, causing
502 errors.
A full restart ensures nginx picks up the new upstream correctly.
The ~1-2 second restart time is acceptable for deployment reliability.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The warmup tool was timing out during CreateGame because the per-step
timeout was only 30 seconds. On a cold JVM (freshly started Eagle
instance during blue-green deployment), CreateGame can take longer
than 30 seconds due to:
- JIT compilation not yet warmed up
- First-time class loading
- Game initialization including Shardok communication
Changes:
- Increase per-operation timeout from 30s to 90s
- Increase overall warmup timeout from 60s to 300s (5 minutes)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1. Add missing jfr and jvm-tmp volumes to eagle-green
- Without these, JFR sidecar can't attach to the JVM when green is active
- JFR recordings wouldn't work either
2. Remove redundant sync_config_files from deploy script
- CI already copies config files via scp before running deploy
- Fetching from GitHub main could cause version mismatches
- Eliminates unnecessary network calls during deployment
3. Skip image pull if already present locally
- CI already pulls images before running deploy script
- Saves time during CI deployments
- Manual deployments still pull if needed
4. Add nginx config validation before reload
- Run nginx -t before nginx -s reload
- Rollback to backup config if validation fails
- Prevents broken config from taking down nginx
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Problems fixed:
1. CI was recreating admin BEFORE blue-green, causing stale .env
2. CI was restarting nginx AFTER blue-green (double restart)
3. Deploy script used slow nginx restart instead of reload
4. Cleanup was blocking the critical path
Changes:
- CI: Only restart shardok before blue-green, let script handle rest
- CI: Remove duplicate nginx restart and fallback path
- Deploy: Use nginx reload (faster) with restart fallback
- Deploy: Update .env before restarting services
- Deploy: Run container cleanup in background
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix: Move S3 backup to BEFORE stopping old server
Critical fix: The old server may wipe games.e0es on shutdown if it
doesn't have the save() merge fix. The backup must happen BEFORE
stopping to capture valid data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Admin console shows all games, auto-install s3cmd, restart nginx properly
1. Admin console now shows all games from games.e0es, not just loaded ones:
- Added getAllRunningGamesSummary() to GamesManager
- Updated getRunningGames() to include unloaded games with "[Not loaded]" status
- Clicking into a game triggers lazy loading via getGameHistory()
2. Deploy script improvements:
- Auto-install s3cmd if not present (via pip or apt)
- Auto-configure s3cmd for DigitalOcean Spaces from .env
- Use nginx restart instead of reload to ensure all workers pick up new config
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix critical bug: save() now merges with existing games.e0es
CRITICAL BUG FIX: With lazy loading, gameControllerInfos only contains
games that have been loaded into memory. The old save() would overwrite
games.e0es with only the loaded games, losing all unloaded games.
This caused complete data loss of user-to-game mappings when:
1. New server started (empty gameControllerInfos)
2. Any operation triggered save()
3. games.e0es was overwritten with empty data
Fix: save() now reads existing games.e0es first and merges:
- Unloaded games are preserved from disk
- Loaded games use fresh in-memory state
Also adds logging to diagnose games.e0es read failures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add S3 backup of games.e0es during blue-green deployment
After stopping the old container (which flushes state to storage),
create a timestamped backup in S3 before switching nginx traffic.
This provides a recovery point if something goes wrong during deployment.
Backups are stored at: s3://eagle0/eagle/save/backups/games.e0es.<timestamp>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix blue-green deployment dependencies and auth routing
1. docker-compose.prod.yml:
- Remove eagle-blue from nginx depends_on (use EAGLE_ADDR variable instead)
- Remove eagle-blue from admin depends_on (use EAGLE_ADDR variable instead)
- Add TODO note about jfr-sidecar limitation during green deployments
2. nginx/nginx.conf:
- Fix auth.Auth location on port 443 to route to auth:40033 instead of
eagle_backend. This was causing auth failures when clients connected
via the main HTTPS port.
These changes allow blue-green deployments to work without hard
dependencies that cause docker-compose to recreate stopped containers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add jfr-sidecar-green for blue-green JFR profiling support
- Add jfr-sidecar-green service that shares PID namespace with eagle-green
- Make admin's jfr-sidecar address configurable via JFR_SIDECAR_ADDR env var
- Update deploy script to:
- Start appropriate jfr-sidecar with each eagle instance
- Update .env with EAGLE_ADDR and JFR_SIDECAR_ADDR after switching
- Restart admin service to pick up new addresses
- Clean up old jfr-sidecar when removing old eagle instance
This ensures the JFR button in admin console works regardless of
whether blue or green is the active instance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Sync config files from GitHub at start of deployment
Downloads docker-compose.prod.yml and nginx.conf from the main branch
before starting deployment. This ensures new services (like jfr-sidecar-green)
are available when the deploy script runs.
- Preserves the current active instance in nginx.conf
- Creates .bak backups before overwriting
- Continues with existing files if GitHub fetch fails
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix: Move S3 backup to BEFORE stopping old server
Critical fix: The old server may wipe games.e0es on shutdown if it
doesn't have the save() merge fix. The backup must happen BEFORE
stopping to capture valid data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix critical bug: save() now merges with existing games.e0es
CRITICAL BUG FIX: With lazy loading, gameControllerInfos only contains
games that have been loaded into memory. The old save() would overwrite
games.e0es with only the loaded games, losing all unloaded games.
This caused complete data loss of user-to-game mappings when:
1. New server started (empty gameControllerInfos)
2. Any operation triggered save()
3. games.e0es was overwritten with empty data
Fix: save() now reads existing games.e0es first and merges:
- Unloaded games are preserved from disk
- Loaded games use fresh in-memory state
Also adds logging to diagnose games.e0es read failures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add S3 backup of games.e0es during blue-green deployment
After stopping the old container (which flushes state to storage),
create a timestamped backup in S3 before switching nginx traffic.
This provides a recovery point if something goes wrong during deployment.
Backups are stored at: s3://eagle0/eagle/save/backups/games.e0es.<timestamp>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix blue-green deployment dependencies and auth routing
1. docker-compose.prod.yml:
- Remove eagle-blue from nginx depends_on (use EAGLE_ADDR variable instead)
- Remove eagle-blue from admin depends_on (use EAGLE_ADDR variable instead)
- Add TODO note about jfr-sidecar limitation during green deployments
2. nginx/nginx.conf:
- Fix auth.Auth location on port 443 to route to auth:40033 instead of
eagle_backend. This was causing auth failures when clients connected
via the main HTTPS port.
These changes allow blue-green deployments to work without hard
dependencies that cause docker-compose to recreate stopped containers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add jfr-sidecar-green for blue-green JFR profiling support
- Add jfr-sidecar-green service that shares PID namespace with eagle-green
- Make admin's jfr-sidecar address configurable via JFR_SIDECAR_ADDR env var
- Update deploy script to:
- Start appropriate jfr-sidecar with each eagle instance
- Update .env with EAGLE_ADDR and JFR_SIDECAR_ADDR after switching
- Restart admin service to pick up new addresses
- Clean up old jfr-sidecar when removing old eagle instance
This ensures the JFR button in admin console works regardless of
whether blue or green is the active instance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Sync config files from GitHub at start of deployment
Downloads docker-compose.prod.yml and nginx.conf from the main branch
before starting deployment. This ensures new services (like jfr-sidecar-green)
are available when the deploy script runs.
- Preserves the current active instance in nginx.conf
- Creates .bak backups before overwriting
- Continues with existing files if GitHub fetch fails
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
With lazy game loading (merged in #5223), games are loaded on-demand
when users reconnect after nginx switches traffic. No explicit reload
call is needed - the new server reads fresh state from storage.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add optional Ed25519 signing to the manifest_manager. When a signing key
is provided, the manifest is signed and the signature is prepended as
a header comment that clients can verify.
Changes:
- manifest_manager: Accept optional private key file as 3rd argument
- manifest_manager: Sign manifest content and prepend signature line
- installer_build.yml: Pass MANIFEST_SIGNING_KEY to manifest_manager
- unity_build.yml: Pass MANIFEST_SIGNING_KEY to manifest_manager
- Add generate_manifest_keys.go script to create key pairs
The signature line format is: # signature=<base64-encoded-ed25519-signature>
To enable signing:
1. Run: go run scripts/generate_manifest_keys.go
2. Add the private key as GitHub secret MANIFEST_SIGNING_KEY
3. Embed the public key in the installer for verification (PR 4)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Apple's CloudKit can have a brief delay after notarization completes
before the ticket is available for stapling. This adds a retry loop
with 10-second delays, up to 5 attempts.
Error was:
CloudKit query for eagle0.app failed due to "Record not found".
The staple and validate action failed! Error 65.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Improves Sparkle sign_update error reporting by capturing stderr, and allows full signing/notarization/deploy pipeline on feature branches via workflow_dispatch.
1. Fix crane installation - don't try to mv crane to itself
(tar extracts to cwd which is already /opt/eagle0)
2. Keep crane binary after deploy for blue-green script to use
3. Update blue-green deploy to use crane instead of docker pull
(fixes OCI/Docker digest mismatch issue)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Fix codesign script to sign all Sparkle framework components
Sign XPC services, nested apps (Updater.app), and standalone
executables (Autoupdate) before signing the framework itself.
Apple notarization requires all nested binaries to be signed
with Developer ID certificate and secure timestamp.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add missing path triggers for pull_request in Mac build workflow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Re-add rules_swift for Mac GoDice plugin build
The DarwinGodiceBundle requires rules_swift to build.
This was inadvertently removed in #5194.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Handles intermittent Docker registry digest mismatch errors like:
"failed commit on ref: unexpected commit digest"
This is a known Docker/containerd issue that can occur due to:
- Registry caching
- Network/proxy issues
- Race conditions during push
Now retries up to 3 times with 5 second delays.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Unity Mac build scripts (build_mac.sh, build_unity_mac.sh)
- Code signing with Developer ID certificate
- Apple notarization for Gatekeeper compliance
- Sparkle framework injection for delta auto-updates
- Go build handler for S3 upload and appcast.xml generation
- Update InvitationCodeManager.cs for Mac platform paths
- GitHub Actions workflow triggered on main branch pushes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add blue-green deployment infrastructure for Eagle server
- Add ReloadGames RPC to eagle.proto for reloading game state from disk
- Implement reloadAllGames() and flushToDisk() in GamesManager.scala
- Add reloadGames() handler to EagleServiceImpl.scala
- Update docker-compose.prod.yml with eagle-blue/green services
- Update nginx.conf for switchable upstream
- Create scripts/deploy-blue-green.sh for zero-downtime deployment
- Create Go warmup tool (src/main/go/net/eagle0/warmup) that:
- Uses bidirectional streaming to create test games
- Posts Improve command and verifies ActionResults
- Cleans up test game after warmup
- Create scripts/warmup-eagle.sh wrapper that uses Go tool or falls back to grpcurl
- Update docker_build.yml to build and deploy warmup binary
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add X-Warmup-User header support with localhost restriction
- AuthorizationInterceptor: Accept X-Warmup-User header for warmup authentication
- Only allow X-Warmup-User from localhost connections (security)
- Go warmup tool: Send x-warmup-user metadata header
- Add grpc/metadata dependency to warmup BUILD
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add HERO_STAT_GAINED (151) and PROFESSION_GAINED (152) to
action_result_type.proto to maintain parity with the Scala enum
- Remove update-action-result-types pre-commit hook and script
(no longer needed with simplified Scala enum approach)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Next steps for productionization
* Run deploy job on self-hosted runner for secure SSH
* Temporarily disable production environment to debug runner
* Use ubuntu-latest for deploy job
* Add remote_tags to oci_push for latest tag
- Add a "Bottom Line Up Front" section after the title with a short prose
paragraph highlighting the most important changes and what to look for
when testing
- Wrap HTML output in proper document with UTF-8 charset declaration to
fix Unicode character rendering (em-dashes, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Update the Claude prompt to generate a "PR Details" section after the synopsis,
with the same thematic groupings but listing actual PR numbers, titles, and
clickable GitHub links.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Switch from Mac Mail AppleScript to Fastmail JMAP API
- Generate HTML synopsis instead of plain text for better formatting
- Auto-fetch account ID, identity ID, and drafts mailbox from API
- Support config files in ~/.config/eagle0/:
- fastmail_token: API token (required)
- changelog_recipient: Email recipients, one per line (optional)
- Support multiple recipients (one email address per line, # for comments)
- Fall back to FASTMAIL_API_TOKEN environment variable for token
- Only require token when not in --dry-run mode
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Creates scripts/generate_changelog.sh that:
- Fetches merged PRs since last run (tracked via git tag) or previous Friday 4pm
- Uses Claude CLI to generate a themed synopsis of changes
- Opens an email draft in Mac Mail with the synopsis
- Updates the changelog-last-run tag for next run
Usage: ./scripts/generate_changelog.sh [--dry-run]
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
The previous hook ran gazelle but didn't check if it modified any files.
This meant commits could go through with non-canonical BUILD files, causing
gazelle_test to fail in CI.
The new wrapper script:
1. Runs gazelle
2. Checks if any BUILD files were modified
3. Fails with a helpful message if they were, instructing the user to stage changes
Also adds a Pre-Commit Checklist section to CLAUDE.md documenting this behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Google Sheets exports TSV files with Windows-style CRLF line endings.
This causes spurious git diffs when the download scripts are run.
Pipe curl output through `tr -d '\r'` to strip carriage returns.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
Replace deprecated --noincompatible_enable_cc_toolchain_resolution flag
with --config=mactools to properly use Apple's Xcode toolchain instead
of LLVM for Darwin bundle builds.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
* missing dep
* cleanup
* Add AI Performance Runner implementation plan
Create comprehensive plan for automated AI performance testing tool that
replicates the manual "Perf" button testing from Unity client. The tool
will provide reproducible performance measurements without requiring
client interaction.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* slow progress
* getting there
* it runs
* it runs
* fully runs
* fully runs
* omg is it working
* removed a lot of loggin
* summary data
* Update AI performance runner to use CommandChoiceResults metrics
- Replace timing-based metrics with search depth and evaluation counts
- Use CommandChoiceResults returned by ShardokAIClient methods
- Display key performance metrics: depth achieved, commands evaluated vs available
- Calculate average search depth and evaluation rate across turns
- Show turn-by-turn breakdown with command types chosen
- Remove obsolete timing measurements in favor of AI budget-based metrics
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add evaluation rate by depth analysis
- Replace meaningless average evaluation rate with depth-specific rates
- Show evaluation percentage at each depth level achieved
- Account for turns that reached higher depths (100% assumed for lower depths)
- Display how many turns reached each depth level
- Provides meaningful insight into time budget utilization at each search level
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Force optimization for AI performance runner binary
- Add -O3 and -DNDEBUG flags to copts for ai_performance_runner binary
- Ensures the performance testing tool always runs optimized regardless of build mode
- Critical for accurate AI performance measurements
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* bad eval
* run gazelle
* Revert copts optimization and add ai_perf_test.sh script
- Revert BUILD.bazel copts changes (insufficient for global optimization)
- Add scripts/ai_perf_test.sh that runs with "bazel run -c opt"
- Script defaults to 10 turns and accepts additional arguments
- Global -c opt dramatically improves AI performance (depth 3 vs depth 2)
- Ensures all AI dependencies are optimized for accurate performance testing
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* review comments
---------
Co-authored-by: Claude <noreply@anthropic.com>
* start the headshot reader
* put in placeholder image paths
* next stage with the generated heroes
* include the full description
* with adjectives
* more variety
* add the image checker
* fixes
* metadata updates
* pretty good
* gazelle
* don't need the tsvfixer
* discard font changes
* oh it's the importpath
* separate package
* set import path
* call the go version
* start the impl
* build file equivalent
* write the loader file too
* write the loader file too
* ignore bazelbsp