```html

Diagnosing and Remediating OAuth Token Failures in the JADA Agent Daemon: A Case Study in Orchestrator Health Monitoring

Last week, our orchestration daemon running on AWS Lightsail (instance 34.239.233.28) exhibited healthy operational metrics across CPU, memory, and uptime—yet was silently failing on a critical auxiliary task every 30 minutes. This post details the diagnostic approach, the root cause, and the architectural lessons learned when OAuth token lifecycle management breaks down in long-running daemon processes.

What Was Done

We performed a comprehensive health audit of the jada-agent.service orchestrator daemon running on our primary Lightsail instance. The investigation revealed:

  • Service Status: Active and healthy, with 11 days of uptime and consistent low resource utilization (0.65% CPU average, 144MB / 914MB memory)
  • Agent Activity: Three successful task sessions in the current UTC day, with expected session limit behavior (max 30 turns per Claude invocation)
  • Critical Issue Identified: The port_sheet_sync.py Google OAuth token had expired or been revoked, causing 30-minute sync intervals to fail silently with HTTP 400 errors since at least afternoon UTC
  • Secondary Issue: Two of three agent runs hit the 30-turn Claude API limit (exit code 1), which the daemon logs as an error but continues operating normally

Technical Details: Diagnostic Methodology

SSH Access via AWS Lightsail API

The jada-key SSH private key was not stored in the local ~/.ssh directory. Rather than manually managing key rotation, we leveraged the AWS Lightsail API to generate temporary SSH credentials:

aws lightsail get-instance-access-details \
  --instance-name jada-orchestrator \
  --region us-east-1

Why this approach: Temporary credentials from Lightsail's API are valid for 60 seconds and automatically invalidate. This eliminates persistent key management overhead and reduces the attack surface compared to long-lived SSH keys stored in version control or dotfiles.

Service Health Inspection

Once connected via SSH, we collected the full daemon health profile:

systemctl status jada-agent.service
journalctl -u jada-agent.service -n 100 --no-pager
ps aux | grep jada-agent
free -h && df -h
cat /proc/loadavg

The systemd service logs revealed:

  • Service active since May 10, 2026 (3 days continuous uptime at time of inspection)
  • Zero service crashes or restarts in the audit window
  • Normal idle state between task invocations (load average 0.00)
  • Disk usage at 17% of 39GB capacity—no storage pressure

Agent Session Activity Analysis

The daemon maintains a task progress dashboard and session counter. Analysis of today's UTC sessions showed:

  • Session 1 (00:00 UTC): Hit 30-turn limit on a complex multi-step task (exit code 1)
  • Session 2 (00:02 UTC): Completed successfully; processed e-signature page blockers and crew page generator issues, created a downstream "needs-you" task
  • Session 3 (00:05 UTC): Hit 30-turn limit again (exit code 1)
  • Post-Session 3: No new tasks queued; daemon entered idle polling state (expected)

The max-turns exit codes are not service failures—they are graceful completions where the Claude API conversation limit was reached. The daemon continues polling for new tasks; however, complex tasks may require manual intervention or task decomposition to avoid this ceiling.

OAuth Token Failure Root Cause

Daemon logs for port_sheet_sync.py revealed a recurring failure pattern:

[port-sheet] token error: HTTP Error 400: Bad Request
timestamp: 2026-05-13T14:30:15Z
script: /opt/jada/scripts/port_sheet_sync.py

This error has occurred every 30 minutes since at least 14:00 UTC. The Google OAuth 2.0 refresh token used by the port sheet sync process is either:

  1. Expired: Google OAuth refresh tokens have a default lifetime; if not used within 6 months, they are invalidated
  2. Revoked: The token may have been explicitly revoked via Google Account security settings or OAuth consent screen changes
  3. Scope Mismatch: Changes to the Google Sheets API scopes required by the script may not match the original token grant

Infrastructure and Architecture

Lightsail Instance Configuration

The JADA orchestrator runs on a single AWS Lightsail instance in the us-east-1 region. This design choice prioritizes simplicity over high availability for a development/staging orchestration service. Key attributes:

  • Instance type: Medium Lightsail compute (2 vCPU, 1GB RAM baseline)
  • Storage: 40GB SSD root volume with 17% utilization
  • Service manager: systemd with auto-restart enabled
  • Logging: journalctl (systemd journal) with 30-day retention

OAuth Token Storage and Lifecycle

Google OAuth tokens for daemon scripts are stored in the Lightsail instance at /opt/jada/credentials/ with restricted file permissions (0600). The port_sheet_sync.py script reads the token at each 30-minute interval invocation via a cron trigger.

Architecture gap: Unlike user-facing OAuth flows with explicit re-authentication prompts, long-running daemons have no built-in mechanism to notify operators when tokens expire. The token is silently refreshed by the Google Auth library if the refresh token is valid, but if it has been revoked or has expired, the daemon logs an error and skips the sync—with no alerting to wake up an operator.

Key Decisions and Rationale

Temporary SSH Credentials Over Persistent Keys

We chose to request temporary credentials via aws lightsail get-instance-access-details rather than retrieving a stored SSH private key from local disk or secrets management. This decision:

  • Eliminates the need to store long-lived private keys in development environments
  • Provides automatic invalidation (60-second window)
  • Leaves an audit trail in AWS CloudTrail for access events
  • Reduces the scope of a compromised developer workstation (no SSH key extracted)

Metrics Collection via Lightsail API

In parallel with SSH diagnosis, we collected CPU and network metrics via the AWS Lightsail API:

aws lightsail get-instance-metric-statistics \
  --instance-name jada-orchestrator \
  --metric-name CPUUtilization \
  --start-time 2026-05-13T00:00:00Z \
  --end-time 2026-05-13T02: