Keep Awake (24-Hour Session)
Automated preparation for unattended Cursor sessions on Cruise Shells. First invocation requires a single Mac Terminal command (one-time setup + tunnel). All subsequent invocations on any VM are fully automatic -- zero manual steps.
Failure modes addressed
| # | Failure Mode | Cause | Automated Fix |
|---|---|---|---|
| 1 | Cruise Shell deleted | "Are you still there?" idle timeout | Playwright triggers Keep Alive via Slack Block Kit |
| 2 | VPN disconnects | Mac sleeps, Cisco VPN DisconnectOnSuspend fires |
Reverse SSH runs caffeinate on Mac |
| 3 | SSH tunnel dies | VPN drop or network interruption | caffeinate + SSH keepalive + Cisco AutoReconnect |
| 4 | Auth tokens expire | Token TTL shorter than session | authcli refresh before starting |
Parallelization guide
Steps 0 and 1a must run first (environment detection + status request). After that:
- Step 1b-1e (Playwright Keep Alive) and Step 2a (reverse SSH test) can start in parallel.
- If Step 2a needs user action (Mac command), start the background watcher (Step 2a-alt) immediately and proceed with Steps 3-5 while waiting. The watcher will auto-complete caffeinate the instant the tunnel appears.
- Steps 3, 4, 5 (VPN, tmux, auth) are all independent and can run in parallel. Steps 3 (VPN) requires Mac SSH, so if tunnel isn't up yet, defer it until watcher succeeds.
Maximize parallelism to complete the skill faster.
Step 0: Environment detection
Verify we're on a Cruise Shell, check for reverse tunnel, and identify the Mac.
echo "CRUISE_SHELLS=$CRUISE_SHELLS"
echo "=== Reverse tunnel check ==="
nc -z -w 1 localhost 2222 2>/dev/null && echo "Reverse tunnel: ACTIVE (port 2222)" || echo "Reverse tunnel: NOT AVAILABLE"
echo "=== Direct SSH check ==="
MAC_IP=$(echo $SSH_CONNECTION | awk '{print $1}')
echo "Mac IP: $MAC_IP"
nc -z -w 3 $MAC_IP 22 2>/dev/null && echo "Direct SSH: REACHABLE" || echo "Direct SSH: UNREACHABLE"
If CRUISE_SHELLS is empty, this is not a Cruise Shell -- skip Step 1.
Mac connectivity priority:
- Reverse tunnel (port 2222 on localhost) -- preferred, works regardless of VPN routing
- Direct SSH to Mac IP -- fallback, only works if GCP can route to VPN client subnet
- Manual tunnel from Mac -- user runs
ssh -N -f -R 2222:localhost:22from Mac Terminal
NEVER skip Step 2. If both automated paths fail, use the ssh -N -f -R fallback
in Step 2a to create the tunnel from the Mac side. Start the background watcher
(Step 2a-alt) in parallel so caffeinate starts automatically the instant the tunnel
appears.
Step 1: Cruise Shell Keep Alive (Playwright)
There is no CLI or text command for Keep Alive. It must be triggered through the Slack Block Kit interactive dropdown via Playwright's React fiber injection.
1a. Request fresh status and check if already active
CallMcpTool: user-slack-ss / slack_send_message
channel_id: "D096E8N8PC5"
message: "status"
Wait 3 seconds, then read the response:
CallMcpTool: user-slack-ss / slack_read_channel
channel_id: "D096E8N8PC5"
limit: 5
Confirm machine shows Status: RUNNING. Note the machine name (e.g. cs-0uz7bqlr-...).
Check if Keep Alive is already active: Look at the Keep Alive: field in the bot
response. If it shows a date 2+ days in the future, Keep Alive is already at or near
the maximum (3 days is the max). In that case, skip Steps 1b-1e entirely and report
the existing expiry date. There is no benefit to re-triggering when it's already maxed.
1b. Ensure Chrome is installed and navigate to Slack DM
Pre-flight: Chrome installation check. Playwright requires a Chrome binary. On fresh Cruise Shells it may not be installed. Check and install before navigating:
if [ -f /opt/google/chrome/chrome ]; then
echo "Chrome: installed ($(google-chrome --version 2>/dev/null || echo 'unknown version'))"
else
echo "Chrome: NOT FOUND -- installing..."
npx playwright install chrome 2>&1 | tail -3
fi
If Chrome is not installed, npx playwright install chrome takes ~60-90 seconds. Use
block_until_ms: 120000 to avoid premature backgrounding.
Pre-flight: Clear stale browser locks. If a prior Playwright session crashed or was interrupted, the Chrome profile may be locked. Clean up before navigating:
pkill -f "chrome.*playwright" 2>/dev/null
rm -f /home/yike.li/.cache/ms-playwright/mcp-chrome-*/SingletonLock 2>/dev/null
echo "Browser locks cleared"
Navigate:
CallMcpTool: user-playwright / browser_navigate
url: "https://app.slack.com/client/E078AUGKMDE/D096E8N8PC5"
Wait 8 seconds (Slack is slow to render Block Kit messages). Take a snapshot:
CallMcpTool: user-playwright / browser_snapshot
Verify the snapshot contains a combobox element (the Keep Alive dropdown). If you see
Loading history... but no combobox, wait 5 more seconds and snapshot again.
1c. Find the correct combobox and open the dropdown
CRITICAL: Multiple comboboxes exist. The DM history contains many status messages,
each with its own Keep Alive combobox. document.querySelector('[role="combobox"]')
returns the FIRST one in DOM order (oldest message), which is almost always wrong.
Step 1: Identify the correct combobox from the snapshot.
Search the snapshot output (from Step 1b) for the machine name noted in Step 1a. The snapshot structure looks like:
- 'listitem "Cruise Shells: Your machines: *cs-XXXXX-...-NNNNNN* ..."':
...
- combobox "None" [ref=eNNNN]
...
- generic [ref=eNNNN]: "Keep Alive:"
Find the listitem whose label contains the current machine name. Inside that listitem,
note the combobox ref. The combobox value will be "None" (not yet active) or a date
string (already active -- check if it's 2+ days in the future before proceeding).
If there are multiple listitem entries for the same machine (from repeated status
commands), use the last one (most recent status message, closest to the bottom of
the snapshot).
Step 2: Click the combobox using its snapshot ref.
CallMcpTool: user-playwright / browser_click
element: "Keep Alive combobox for <machine-name>"
ref: "<combobox ref from snapshot>"
Take a snapshot. If listbox with option elements is visible, proceed to 1d.
Fallback chain (if browser_click doesn't open the dropdown):
Fallback 1: React fiber onClick
CallMcpTool: user-playwright / browser_evaluate
function: |
() => {
const comboboxes = document.querySelectorAll('[role="combobox"]');
const target = comboboxes[comboboxes.length - 1]; // last = most recent status message
if (!target) return 'no combobox found';
const propsKey = Object.keys(target).find(k => k.startsWith('__reactProps$'));
if (propsKey && target[propsKey].onClick) {
target[propsKey].onClick({
preventDefault: () => {}, stopPropagation: () => {},
nativeEvent: new MouseEvent('click', { bubbles: true }),
target: target, currentTarget: target, type: 'click'
});
}
target.focus();
return 'last combobox opened via React onClick';
}
Take a snapshot. If listbox with option elements is visible, proceed to 1d.
Fallback 2: React fiber onFocus + onMouseDown
Some Slack versions wire the dropdown to onFocus or onMouseDown instead of onClick:
CallMcpTool: user-playwright / browser_evaluate
function: |
() => {
const comboboxes = document.querySelectorAll('[role="combobox"]');
const target = comboboxes[comboboxes.length - 1];
if (!target) return 'no combobox';
const propsKey = Object.keys(target).find(k => k.startsWith('__reactProps$'));
if (!propsKey) return 'no React props found';
const props = target[propsKey];
const synth = {
preventDefault: () => {}, stopPropagation: () => {},
nativeEvent: new MouseEvent('mousedown', { bubbles: true }),
target: target, currentTarget: target, type: 'mousedown'
};
if (props.onMouseDown) props.onMouseDown(synth);
if (props.onFocus) props.onFocus({ ...synth, type: 'focus' });
if (props.onClick) props.onClick({ ...synth, type: 'click' });
target.focus();
return { handlers: Object.keys(props).filter(k => k.startsWith('on')) };
}
Take a snapshot. If listbox visible, proceed to 1d.
Fallback 3: Keyboard navigation (MOST RELIABLE as of Apr 2026)
Click the combobox ref first (using browser_click), then press ArrowDown to open:
CallMcpTool: user-playwright / browser_press_key
key: "ArrowDown"
Take a snapshot. This click-then-ArrowDown approach has been the most reliable method for opening the Slack Block Kit dropdown.
If all approaches fail: The combobox text already shows the current Keep Alive expiry date. If it shows any future date, Keep Alive is already active. Report the date and move on. Only escalate to the user if the date is in the past or missing.
1d. Select longest Keep Alive duration
CallMcpTool: user-playwright / browser_evaluate
function: |
() => {
const options = document.querySelectorAll('[role="option"]');
if (!options.length) return 'no options - dropdown may not be open';
const target = options[options.length - 1]; // last = longest duration
const propsKey = Object.keys(target).find(k => k.startsWith('__reactProps$'));
if (!propsKey || !target[propsKey].onClick) return 'no React onClick on option';
target[propsKey].onClick({
preventDefault: () => {}, stopPropagation: () => {},
nativeEvent: new MouseEvent('click', { bubbles: true }),
target: target, currentTarget: target, type: 'click'
});
return { triggered: true, option: target.textContent };
}
This dispatches to Slack's blocks.actions internal API, which forwards the
interaction to the Cruise Shells bot.
1e. Verify bot confirmation
Wait 10 seconds. Read the DM:
CallMcpTool: user-slack-ss / slack_read_channel
channel_id: "D096E8N8PC5"
limit: 5
Expected bot messages (all three must appear):
- "Received request to keep machine ... alive, processing..."
- "Marking your machine ... to be kept alive."
- "Keep Alive request for machine ... has completed."
If missing, retry from Step 1c. If the dropdown option refs changed, take a fresh snapshot to get new refs.
Step 2: Run caffeinate on Mac (Reverse SSH)
The VPN is Cisco Secure Client with DisconnectOnSuspend policy. If the Mac sleeps,
VPN drops immediately and does NOT auto-reconnect until wake. caffeinate prevents
sleep entirely.
One-time Mac setup
The Mac needs Remote Login enabled and a one-time config command. Check if already done by looking for the marker file:
test -f ~/.keep-awake-setup-done && echo "SETUP_DONE" || echo "SETUP_NEEDED"
If SETUP_DONE, skip to Step 2a.
If SETUP_NEEDED, the user must run this single command in Mac Terminal. It is
fully idempotent (safe to run multiple times), handles authorized_keys, SSH config,
and caffeinate in one shot:
mkdir -p ~/.ssh && chmod 700 ~/.ssh && (grep -q 'AAAAC3NzaC1lZDI1NTE5AAAAIPDSohnQQkeFkl0ItZBRyA9hmBMPyZ11GzsYIOafWRAT' ~/.ssh/authorized_keys 2>/dev/null || echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDSohnQQkeFkl0ItZBRyA9hmBMPyZ11GzsYIOafWRAT yike.li@getcruise.com' >> ~/.ssh/authorized_keys) && chmod 600 ~/.ssh/authorized_keys && (grep -q 'RemoteForward 2222' ~/.ssh/config 2>/dev/null || printf '\nHost cs-*\n RemoteForward 2222 localhost:22\n' >> ~/.ssh/config) && echo "Done! Setup complete."
This command does three things:
- Authorizes the Cruise Shell's SSH key (same key across all VMs via persistent home disk)
- Adds
RemoteForward 2222 localhost:22for allHost cs-*connections - Is permanent -- never needs to be re-run unless the Mac is wiped
The Host cs-* wildcard matches ALL Cruise Shell hostnames, so it works across VM
recreates. The SSH key is tied to ~/.ssh/id_ed25519 on the persistent home disk,
so it also survives VM recreates.
Remote Login: The Mac must have Remote Login enabled (System Settings > General > Sharing > Remote Login). GM corporate Macs typically have this on by default.
2a. Detect Mac connectivity and establish tunnel
Try reverse tunnel first, then direct SSH, then create tunnel from Mac.
MAC_USER="KZFZ9H"
if nc -z -w 1 localhost 2222 2>/dev/null; then
echo "Using reverse tunnel (localhost:2222)"
MAC_SSH="ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -p 2222 ${MAC_USER}@localhost"
elif nc -z -w 5 $(echo $SSH_CONNECTION | awk '{print $1}') 22 2>/dev/null; then
MAC_IP=$(echo $SSH_CONNECTION | awk '{print $1}')
echo "Using direct SSH to $MAC_IP"
MAC_SSH="ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes -i ~/.ssh/id_ed25519 ${MAC_USER}@${MAC_IP}"
else
echo "BOTH_PATHS_BLOCKED"
fi
If reverse tunnel works: proceed directly to Step 2b. This is the happy path.
If direct SSH works but reverse tunnel didn't: auto-fix the Mac's SSH config:
$MAC_SSH 'grep -q "RemoteForward 2222" ~/.ssh/config 2>/dev/null && echo "Reverse tunnel config: already present" || {
printf "\nHost cs-*\n RemoteForward 2222 localhost:22\n" >> ~/.ssh/config
echo "Reverse tunnel config: ADDED"
}'
Then proceed to Step 2b.
If BOTH paths are blocked (GCP cannot route to VPN client IPs -- known limitation):
Check if the one-time Mac setup was previously completed:
test -f ~/.keep-awake-setup-done && echo "SETUP_DONE" || echo "SETUP_NEEDED"
If SETUP_DONE: The Mac has the SSH config and authorized_keys, but the current
Cursor SSH connection was established before RemoteForward was added to the config
(or this is a session where GCP routing blocks direct SSH). The fix is to create the
reverse tunnel manually from the Mac without reconnecting Cursor:
ssh -N -f -R 2222:localhost:22 -o StrictHostKeyChecking=no yike.li@CRUISE_SHELL_IP
Give the user this exact command with the Cruise Shell's IP filled in (from the Cruise
Shells bot status message or hostname -I). This creates a background SSH tunnel from
the Mac to the Cruise Shell with the reverse forward on port 2222. Flags: -N = no
remote command, -f = background after auth. The user's Cursor session is untouched.
After the user runs it, verify the tunnel:
nc -z -w 1 localhost 2222 && echo "Tunnel ACTIVE" || echo "Tunnel NOT YET ACTIVE"
Then proceed to Step 2b.
If SETUP_NEEDED: The one-time setup hasn't been done. Give the user the combined
setup + tunnel command for Mac Terminal:
mkdir -p ~/.ssh && chmod 700 ~/.ssh && (grep -q 'AAAAC3NzaC1lZDI1NTE5AAAAIPDSohnQQkeFkl0ItZBRyA9hmBMPyZ11GzsYIOafWRAT' ~/.ssh/authorized_keys 2>/dev/null || echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDSohnQQkeFkl0ItZBRyA9hmBMPyZ11GzsYIOafWRAT yike.li@getcruise.com' >> ~/.ssh/authorized_keys) && chmod 600 ~/.ssh/authorized_keys && (grep -q 'RemoteForward 2222' ~/.ssh/config 2>/dev/null || printf '\nHost cs-*\n RemoteForward 2222 localhost:22\n' >> ~/.ssh/config) && ssh -N -f -R 2222:localhost:22 -o StrictHostKeyChecking=no yike.li@CRUISE_SHELL_IP && echo "Done!"
Fill in CRUISE_SHELL_IP. This does the one-time setup AND creates the tunnel in one command. No Cursor reconnect needed.
After the user runs it, verify the tunnel, then proceed to Step 2b.
CRITICAL: Do NOT tell the user to "reconnect Cursor" or "reload window". The
ssh -N -f -R command creates the tunnel immediately without disrupting anything.
Telling the user to reconnect is a passive 3.25 move.
2a-alt. Background watcher (start in parallel with user action)
While waiting for the user to run the Mac command (if needed), start a background watcher that will detect the tunnel the instant it becomes available:
cat > /tmp/keep_awake_watcher.sh << 'SCRIPT'
#!/bin/bash
MAC_USER="KZFZ9H"
LOG="/tmp/keep_awake_watcher.log"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG"; }
log "=== Keep Awake Watcher started ==="
while true; do
if nc -z -w 1 localhost 2222 2>/dev/null; then
MAC_SSH="ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -p 2222 ${MAC_USER}@localhost"
log "Reverse tunnel ACTIVE! Starting caffeinate..."
RESULT=$($MAC_SSH 'kill $(pgrep caffeinate) 2>/dev/null; sleep 0.5; nohup caffeinate -dimsu -t 86400 </dev/null >/dev/null 2>&1 & echo "PID=$!"; sleep 0.5; pgrep -l caffeinate' 2>&1)
if echo "$RESULT" | grep -q caffeinate; then
log "caffeinate STARTED: $RESULT"
echo "SUCCESS" > /tmp/keep_awake_watcher_status.txt
exit 0
fi
log "caffeinate failed: $RESULT"
fi
MAC_IP=$(echo $SSH_CONNECTION | awk '{print $1}')
if nc -z -w 3 $MAC_IP 22 2>/dev/null; then
MAC_SSH="ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yes -i ~/.ssh/id_ed25519 ${MAC_USER}@${MAC_IP}"
log "Direct SSH reachable! Fixing config + starting caffeinate..."
$MAC_SSH 'grep -q "RemoteForward 2222" ~/.ssh/config 2>/dev/null || printf "\nHost cs-*\n RemoteForward 2222 localhost:22\n" >> ~/.ssh/config' 2>&1
RESULT=$($MAC_SSH 'kill $(pgrep caffeinate) 2>/dev/null; sleep 0.5; nohup caffeinate -dimsu -t 86400 </dev/null >/dev/null 2>&1 & echo "PID=$!"; sleep 0.5; pgrep -l caffeinate' 2>&1)
if echo "$RESULT" | grep -q caffeinate; then
log "caffeinate STARTED via direct SSH: $RESULT"
echo "SUCCESS" > /tmp/keep_awake_watcher_status.txt
exit 0
fi
fi
sleep 30
done
SCRIPT
chmod +x /tmp/keep_awake_watcher.sh
nohup bash /tmp/keep_awake_watcher.sh > /dev/null 2>&1 &
echo "Watcher PID: $!"
The watcher checks every 30 seconds and auto-starts caffeinate the moment either path becomes available. It exits after success. Check status via:
cat /tmp/keep_awake_watcher.log
cat /tmp/keep_awake_watcher_status.txt 2>/dev/null
2b. Mark setup complete and start caffeinate
After ANY successful SSH to the Mac (reverse tunnel or direct), mark the one-time setup as done so future invocations skip the setup prompt:
touch ~/.keep-awake-setup-done
Kill existing caffeinate and start fresh with 24-hour timer:
Always kill and restart. An existing caffeinate may have been started with an older/shorter timer that could expire mid-session.
MAC_USER="KZFZ9H"
if nc -z -w 1 localhost 2222 2>/dev/null; then
MAC_SSH="ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -p 2222 ${MAC_USER}@localhost"
else
MAC_IP=$(echo $SSH_CONNECTION | awk '{print $1}')
MAC_SSH="ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -i ~/.ssh/id_ed25519 ${MAC_USER}@${MAC_IP}"
fi
$MAC_SSH 'kill $(pgrep caffeinate) 2>/dev/null; sleep 0.5; nohup caffeinate -dimsu -t 86400 </dev/null >/dev/null 2>&1 & echo "caffeinate PID: $!"; sleep 0.5; pgrep -l caffeinate'
Flags: -d display, -i idle, -m disk, -s system, -u user activity.
Duration: 86400s = 24 hours.
2c. Verify Mac power and sleep state
MAC_USER="KZFZ9H"
if nc -z -w 1 localhost 2222 2>/dev/null; then
MAC_SSH="ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -p 2222 ${MAC_USER}@localhost"
else
MAC_IP=$(echo $SSH_CONNECTION | awk '{print $1}')
MAC_SSH="ssh -o ConnectTimeout=5 -i ~/.ssh/id_ed25519 ${MAC_USER}@${MAC_IP}"
fi
$MAC_SSH '
echo "=== Power ===" && pmset -g ps | head -2
echo "=== Sleep prevention ===" && pmset -g | grep "sleep "
'
Required: Output must show AC Power (not battery) and sleep prevented by caffeinate.
If on battery, warn the user to plug in.
Step 3: Verify VPN health
(Can run in parallel with Steps 4 and 5)
MAC_USER="KZFZ9H"
if nc -z -w 1 localhost 2222 2>/dev/null; then
MAC_SSH="ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -p 2222 ${MAC_USER}@localhost"
else
MAC_IP=$(echo $SSH_CONNECTION | awk '{print $1}')
MAC_SSH="ssh -o ConnectTimeout=5 -i ~/.ssh/id_ed25519 ${MAC_USER}@${MAC_IP}"
fi
$MAC_SSH '
echo "=== VPN process uptime ===" && ps -eo pid,lstart,etime,comm | grep vpnagentd | grep -v grep
echo "=== AutoReconnect config ===" && grep -A1 "AutoReconnect" /opt/cisco/secureclient/vpn/profile/GM-Cruise.xml 2>/dev/null
'
The VPN is Cisco Secure Client (not GlobalProtect). Key config:
AutoReconnect: true-- recovers from brief network interruptionsDisconnectOnSuspend-- disconnects on Mac sleep (neutralized by caffeinate)- No client-side session timeout -- server-side timeout is long (proven 2+ day uptime)
Step 4: Set up tmux (optional)
(Can run in parallel with Steps 3 and 5)
For terminal processes that should survive a brief SSH reconnect:
tmux has-session -t overnight 2>/dev/null \
&& echo "tmux session 'overnight' already exists" \
|| tmux new-session -d -s overnight
Note: Cursor agent state CANNOT be recovered after SSH disconnect. tmux only preserves terminal processes, not the Cursor agent conversation.
Step 5: Verify auth credentials
(Can run in parallel with Steps 3 and 4)
TOKEN=$(gcloud auth application-default print-access-token 2>&1 | head -1)
if [[ "$TOKEN" == ya29.* ]]; then echo "gcloud: OK"; else echo "gcloud: EXPIRED"; fi
echo "---"
authcli status 2>&1 | head -30 || true
Note: pipe authcli status through || true to avoid SIGPIPE exit code 141 from head.
If Cruise User Credentials or gcloud are expired, follow the cruise-auth-refresh skill.
If only non-critical app tokens are expired, note them but don't block on them.
Summary checklist
Report to the user after all steps complete:
- Keep Alive: Active (report expiry date -- max is 3 days)
- Reverse tunnel: Active on port 2222 (or watcher running if pending)
- caffeinate: Running on Mac via reverse SSH (PID confirmed, 24-hour timer)
- Mac power: On AC power, sleep prevented
- VPN health: Cisco Secure Client up, AutoReconnect enabled
- tmux: Session created (optional)
- Auth: gcloud and Cruise User Credentials valid (note any expired app tokens)
- Setup marker:
~/.keep-awake-setup-doneexists (future runs skip setup prompt)
Troubleshooting
Chrome not installed (Playwright fails immediately)
Error: Chromium distribution 'chrome' is not found at /opt/google/chrome/chrome
Run npx playwright install chrome. This downloads and installs Chrome (~60-90s). Use
block_until_ms: 120000 to prevent premature backgrounding. Step 1b now includes this
check automatically.
Browser locked ("Browser is already in use")
Error: Browser is already in use for .../mcp-chrome-..., use --isolated
A prior Playwright session crashed or was interrupted. Fix:
pkill -f "chrome.*playwright" 2>/dev/null
rm -f /home/yike.li/.cache/ms-playwright/mcp-chrome-*/SingletonLock 2>/dev/null
Then retry browser_navigate. Step 1b now includes this cleanup automatically.
Keep Alive Playwright fails
- Already at max: The combobox text shows a date 2-3 days in the future. The dropdown won't open because Keep Alive is already at the maximum. This is fine -- skip it.
- No combobox found: The status message may not have loaded. Send
statusto the bot DM again via Slack MCP, wait 5s, then re-navigate Playwright. - Multiple comboboxes: The DM history contains many status messages, each with its
own Keep Alive combobox. DO NOT use
document.querySelector('[role="combobox"]')-- it returns the first (oldest) one. Instead, find the correct combobox ref from the Playwright snapshot by searching for the machine name insidelistitemlabels. - Dropdown opens but option click has no effect: If the React fiber key suffix
changed (page reload), take a fresh snapshot and re-query
__reactProps$. - None of the fallback approaches open the dropdown: If the combobox shows any future date, Keep Alive is active. Report the date and move on. Only escalate if the date is in the past or missing entirely.
Reverse tunnel not available (port 2222 closed)
- SSH config missing + direct SSH works: Auto-fix it (see Step 2a). Do NOT tell the user to run the command manually.
- SSH config missing + direct SSH blocked: Have the user run the combined setup + tunnel command from Step 2a. This does the one-time config AND creates the tunnel in a single paste. No Cursor reconnect needed.
- Config present but tunnel not active: The current Cursor SSH session was
established before
RemoteForwardwas in the config. Have the user create the tunnel directly:ssh -N -f -R 2222:localhost:22 -o StrictHostKeyChecking=no yike.li@CRUISE_SHELL_IP. Do NOT tell the user to reconnect Cursor -- that's disruptive and unnecessary. - Cursor reconnected but tunnel didn't start: Verify the config was written
correctly by SSHing to the Mac and running
grep RemoteForward ~/.ssh/config. - Port 2222 already in use on Cruise Shell: Another process is using the port. Change to a different port (e.g., 2223) in both the SSH config and the skill commands.
Direct SSH to Mac fails (fallback path)
- Connection timed out: GCP Cruise Shells cannot route to VPN client IPs (10.249.x.x,
10.252.x.x). This is a known network limitation since ~March 2026. Do NOT give up.
Fall back to the
ssh -N -f -Rapproach: have the user create the tunnel from their Mac (see Step 2a). Start the background watcher (Step 2a-alt) in parallel. - Permission denied: The Cruise Shell's pubkey isn't in Mac's
~/.ssh/authorized_keys. Give the user the one-time setup command from Step 2 (it includes authorized_keys + SSH config + tunnel creation in a single paste). - Connection refused: Mac doesn't have Remote Login enabled. Tell user: System Settings > General > Sharing > Remote Login > ON.
- Wrong username: GM corporate Macs use a short ID (e.g.
KZFZ9H), not the Cruise Shell username. Detect by trying common patterns or asking the user forwhoamioutput.
Both paths blocked -- decision tree
When BOTH reverse tunnel and direct SSH fail, follow this decision tree:
Is ~/.keep-awake-setup-done present?
├── YES: One-time setup was done before.
│ Give user SHORT command (just the tunnel, ~30 chars):
│ ssh -N -f -R 2222:localhost:22 -o StrictHostKeyChecking=no yike.li@<IP>
│ Start background watcher in parallel.
│
└── NO: First time ever.
Give user FULL command (setup + tunnel, single paste):
mkdir -p ~/.ssh && chmod 700 ~/.ssh && ... && ssh -N -f -R ...
Start background watcher in parallel.
NEVER say "skip caffeinate" or "will self-heal next session." Always provide the user with an actionable command and start the background watcher.
caffeinate dies mid-session
If caffeinate terminates (e.g., another process kills it), the Mac can sleep and VPN drops.
To guard against this, you can start caffeinate with while true loop:
MAC_USER="KZFZ9H"
if nc -z -w 1 localhost 2222 2>/dev/null; then
MAC_SSH="ssh -o ConnectTimeout=5 -p 2222 ${MAC_USER}@localhost"
else
MAC_IP=$(echo $SSH_CONNECTION | awk '{print $1}')
MAC_SSH="ssh -o ConnectTimeout=5 -i ~/.ssh/id_ed25519 ${MAC_USER}@${MAC_IP}"
fi
$MAC_SSH 'nohup bash -c "while true; do caffeinate -dimsu -t 3600; done" </dev/null >/dev/null 2>&1 &'
This restarts caffeinate every hour indefinitely.