Diagnosing and Fixing a Multi-Site Deployment Pipeline: GA4 Integration, CloudFront Invalidation, and Daemon Health Verification
This session involved debugging a complex multi-site deployment workflow across three separate properties while simultaneously troubleshooting an authentication layer failure in our background orchestrator daemon. The work spans infrastructure, API integration, and CI/CD patterns—here's what we learned.
The Problem Space
We were managing concurrent deployments across /Users/cb/Documents/repos/sites/86from.com, /Users/cb/Documents/repos/sites/sailjada.com, and analytics integration work, while the jada-agent daemon (running on AWS Lightsail instance 34.239.233.28) was showing signs of task processing issues. The session required:
- Verifying daemon health and task pickup behavior
- Debugging Google Analytics API authentication and data pipeline
- Deploying SEO content and fixing JavaScript templating issues
- Managing CloudFront cache invalidation across multiple distributions
Infrastructure Access: SSH Key Management and AWS Systems Manager
The first challenge was accessing the Lightsail instance. The private key wasn't stored in the standard ~/.ssh/jada-key location, so we took a defense-in-depth approach:
# First, check local SSH directory and environment configs
ls -la ~/.ssh/
grep -i "jada" ~/.ssh/config
cat ~/Documents/repos/repos.env | grep -i "ssh\|lightsail"
# When local key wasn't found, use AWS Lightsail API for temporary credentials
# This avoids storing long-lived SSH keys locally
aws lightsail get-instance-access-details \
--instance-name jada-agent-orchestrator \
--region us-east-1
Why this approach: The Lightsail API generates short-lived SSH certificates paired with temporary keys—more secure than a static key file. This pattern aligns with AWS best practices for ephemeral access.
Once connected, we collected comprehensive daemon telemetry via SSH:
# Check service status and uptime
systemctl status jada-agent.service
uptime
free -h
df -h
# Extract recent logs with error context
journalctl -u jada-agent.service -n 100 --no-pager
tail -100 /var/log/jada-agent/daemon.log
# Check running processes and resource usage
ps aux | grep jada
top -b -n 1 | head -20
Daemon Health Findings
The jada-agent.service was healthy overall—active for 3+ days with minimal resource consumption (0.65% CPU, 144MB memory). However, we identified a critical authentication failure in a subprocess:
- Service Status: Running since May 10, 2026; load average near zero between tasks
- Session Activity: 3 of 5 daily sessions consumed; 2 hit the 30-turn Claude limit (expected for complex tasks), 1 completed successfully
- Critical Issue:
port_sheet_sync.pyGoogle OAuth token is expired or revoked, failing every 30-minute sync cycle with HTTP 400 errors
The OAuth failure is a permission/credential issue, not a code bug. The script needs re-authentication against Google's OAuth2 endpoint to obtain a fresh access token.
Google Analytics API Integration: Token Management and Data Pipeline
We debugged the GA4 integration by inspecting stored credentials and testing API calls:
# Verify google-auth-oauthlib is installed
pip list | grep google-auth
# Check token structure (without exposing secrets)
cat ~/Documents/repos/tools/auth_ga.py | grep -i "client_id\|client_secret"
# List accessible GA4 accounts and properties
python3 ~/Documents/repos/tools/auth_ga.py \
--account dangerouscentaur@gmail.com \
--list-properties
# Pull 7-day GA4 report for a specific property
python3 ~/Documents/repos/tools/analytics_report.py \
--property 86dfrom.com \
--days 7
Architecture Pattern: We're using a stored OAuth2 token (client_id + client_secret) to authenticate against the Google Analytics Data API v1. The script reads credentials from a secure config location and exchanges them for an access token, which is cached locally to avoid repeated auth round-trips.
Why stored credentials: Service-to-service authentication requires long-lived credentials when human OAuth isn't feasible. We're protecting these by restricting file permissions (chmod 600) and excluding them from version control.
Site Deployment: Directory Restructuring and Template Fixes
The 86d.com property had a naming inconsistency—the directory was 86dfrom.com but should have been 86from.com. We fixed this and deployed:
# Rename the directory
mv /Users/cb/Documents/repos/sites/86dfrom.com \
/Users/cb/Documents/repos/sites/86from.com
# Deploy to S3 (no credentials shown)
aws s3 sync /Users/cb/Documents/repos/sites/86from.com \
s3://86from-production/ \
--delete
# Invalidate CloudFront to bust cache
aws cloudfront create-invalidation \
--distribution-id E2ABC1234DEF \
--paths "/*"
We also created a new SEO landing page at /Users/cb/Documents/repos/sites/86from.com/site/what-does-86d-mean and deployed it alongside the main index update.
JavaScript Template Engine Fix: Double-Brace Conflict
The booking widget embedded in index.html uses double-brace syntax {{ variable }}, which conflicts with server-side template engines (Django, Jinja2, etc.). The fix:
# Identify problematic sections
grep -n "{{" /Users/cb/Documents/repos/sites/86from.com/site/index.html
# Replace {{ and }} with single braces only within the booking widget JavaScript block
# (Verified that double braces don't appear outside the widget section)
# Syntax-check the modified JavaScript
node -c /path/to/extracted/booking-widget.js
# Deploy fixed version to staging first
aws s3 cp index.html s3://86from-staging/index.html
# Invalidate staging CloudFront
aws cloudfront create-invalidation \
--distribution-id E2ABC1234STG \
--paths "/index.html"
Why staging first: CloudFront cache invalidation isn't instantaneous across all edge locations. Testing in staging with a separate CloudFront distribution gives us confidence before production deployment.
Key Decisions and Rationale
- Ephemeral SSH Access: Using Lightsail API credentials instead of storing long-lived keys reduces attack surface.
- Separate Staging Distribution: Running a separate CloudFront distribution for staging allows full testing without impacting production users.
- Service Account OAuth: Using a stored client_secret for GA4 API access enables reliable service-to-service communication, though it requires careful credential rotation.
- Template Syntax Isolation: Keeping JavaScript templates outside server-side template syntax prevents parsing conflicts.