Error in user YAML: (<unknown>): mapping values are not allowed in this context at line 2 column 208
---
log:
2026-06-15: Switched the keep-alive daemon from the `colab.pa.googleapis.com` `RuntimeService/KeepAliveAssignment` RPC to a Tunnel Frontend HTTP ping (`GET /tun/m/<endpoint>/keep-alive/` with `X-Colab-Tunnel: Google`) on `colab.research.google.com`. The RPC required `serviceusage` consumer access to Colab's internal project `1014160490159`, which ordinary user accounts lack, so every external user hit HTTP 403 `USER_PROJECT_DENIED` and their CLI sessions were idle-pruned within minutes (issue #14). Reproduced live with a third-party account; verified the tunnel ping is accepted by the same bearer-token credential that already works for `assign`. A `ReadTimeout` on the ping is treated as success (TFE records activity before forwarding to the often-non-responding VM). Generalized the pre-flight remediation messaging away from the now-irrelevant `colaboratory`/`pa.googleapis.com` framing, and removed the dead grpc-web client-registry/API-key code.
2026-06-10: Replaced the POSIX-only `fcntl.flock` file locking in `_LockedFileStore` with the cross-platform `filelock` library (reported broken on Windows). Reads use `ReadWriteLock.read_lock()` (shared) and writes use `write_lock()` (exclusive), preserving the original `LOCK_SH`/`LOCK_EX` semantics. The lock is constructed with `is_singleton=False` so two `StateStore` instances for the same path in one process don't collapse into a single reentrant lock (which would raise `RuntimeError` on multi-threaded write contention). Added shared-read, cross-process exclusion, and multi-thread/multi-process regression tests.
---
Session management involves interacting with the Colab backend to allocate, monitor, and terminate runtimes.
The colab new command supports selecting specific hardware and runtime environments. Based on the tpu-v5e1.har trace and colab-agent source code, the following parameters and values are identified:
Defines the general class of hardware requested.
DEFAULT: Standard CPU-based runtime.GPU: Request a GPU-accelerated runtime.TPU: Request a TPU-accelerated runtime.
Defines the specific hardware model.
- None: For
DEFAULTvariant. - GPU Accelerators:
T4: NVIDIA T4 (standard free-tier GPU).L4: NVIDIA L4 (cost-effective modern GPU).A100: NVIDIA A100 (high-performance GPU).H100: NVIDIA H100 (latest-gen performance GPU).
- TPU Accelerators:
V2-8: TPU v2 (8 cores).V5E1: TPU v5e (1 core, optimized for inference/efficient training).V6E1: TPU v6e (1 core, high performance).
The CLI maps user flags to these backend parameters:
colab new my-session->variant=DEFAULT,accelerator=NONEcolab new my-session -gpu=L4->variant=GPU,accelerator=L4colab new my-session -tpu=v5e1->variant=TPU,accelerator=V5E1
- API:
GET https://colab.sandbox.google.com/tun/m/assign(based on HAR). - Parameters:
nbh: Notebook hash. Generated from a unique UUID per CLI session/client instance, transformed to web-safe base64 with specific padding (44 characters total).nsa: 1 (Standard flag observed in browser traces, typically for "next-gen session architecture").variant: Selected from the list above.accelerator: Selected from the list above.
- State Persistence: The response contains a
tokenand potentially a backend URL or identifier. We will store this in a local JSON file (default~/.config/colab-cli/sessions.json).- Format:
{ "session_name": { "token": "...", "backend_url": "...", "hardware": "..." } }
- Format:
- API:
/api/sessionsor querying the kernel for resource usage via a special "status" message. - Metric Collection: Execute a small snippet on the VM to get memory/CPU usage if the backend API doesn't provide it directly.
- API:
POST https://colab.sandbox.google.com/tun/m/unassign/<endpoint>(based ontpu-v5e1-unassign.har). - Flow:
- Perform a
GETrequest to the unassign URL to obtain a fresh XSRF token. - Perform a
POSTrequest to the same URL with theX-Goog-Colab-Tokenheader.
- Perform a
- Parameters:
authuser: 0.<endpoint>: The unique session identifier returned during assignment (e.g.,tpu-v5e1-s-kkb-...).
- Cleanup: Remove the session from the local state file upon successful 204 response.
- API:
GET https://colab.research.google.com/tun/m/assignments(based oncolab-agentimplementation). - Function: Lists all active VM assignments for the user. This is useful for synchronizing local state with actual backend sessions.
To prevent Colab VMs from being deleted due to idle timeouts (standard is ~90 minutes), the CLI implements a background keep-alive mechanism.
- Daemon Process: Since the CLI is a fire-and-forget tool,
colab newspawns a detached background process running a hiddenkeep-alivecommand. - Tunnel ping: Every 60 seconds, the daemon issues
GET https://colab.research.google.com/tun/m/<endpoint>/keep-alive/with the headerX-Colab-Tunnel: Google, authenticated with the user's own Gaia bearer token (the same credential and host used for/tun/m/assign). The Tunnel Frontend (TFE) recordsLastActiveTimebefore forwarding the request, which refreshes the idle timer. This matches the officialcolab-vscodeextension'ssendKeepAlive. TFE notes the activity on arrival and then forwards to the VM, which often does not answer on this path — so the request commonly read-times-out even though the keep-alive succeeded; aReadTimeoutis therefore treated as success, while genuine HTTP errors (e.g. 404 for a deleted assignment) propagate.- Why not the RuntimeService RPC: The previous implementation called
google.internal.colab.v1.RuntimeService/KeepAliveAssignmentatcolab.pa.googleapis.comwithX-Goog-User-Project: 1014160490159. That path requires the caller to be aserviceusageconsumer of Colab's internal project1014160490159, which no ordinary user account is — so it returned HTTP 403USER_PROJECT_DENIEDfor every external user, causing CLI sessions to be idle-pruned within minutes (issue #14). Dropping the header instead produced HTTP 400CONSUMER_INVALID(public API-key project ≠ bearer-token quota project). The browser only succeeds because it rides the user'sgoogle.comcookie through an internal cookie-proxy (colab.clients6.google.com), which a headless bearer-token client cannot use. The TFE tunnel ping needs no project entitlement and works for any account that can assign a VM.
- Why not the RuntimeService RPC: The previous implementation called
- Pre-flight (
colab new, OAuth2/ADC only): Immediately after a successfulassign, the CLI invokeskeep_alive_assignmentonce synchronously. If the response is 403 with aSCOPE_NOT_PERMITTEDbody, it unassigns the new VM (to avoid leaking a billable assignment) and prints a per-provider remediation message before exiting non-zero. Other errors are tolerated — the daemon will retry and surface them via the structured event log. (Because keep-alive now uses the same backend/credential asassign, a scope failure at this stage is rare — assignment would normally have failed first.) - Structured logging: The daemon emits
keep_alive_started(withpid,endpoint), onekeep_alive_errorper failed iteration (withstatus_code,error_type, truncatederror,response_body,iteration,consecutive_4xx), andkeep_alive_stopped(withreason,iterations,duration_seconds, optionallast_error, optionalexpected_endpoint/actual_endpoint). All three are rendered specially bycolab logso users get diagnostic context without parsing JSONL by hand. - Termination:
- Explicit:
colab stopterminates the daemon using its stored PID. - Implicit: If a session is pruned (e.g., during
sync_sessions), its daemon is also terminated. - Safety Fallback: The daemon automatically terminates after 24 hours to prevent permanent zombie processes.
- State Check: The daemon periodically verifies that its session still exists in the local state store; if missing, it exits.
- Repeated 4xx: After two consecutive 4xx responses, the daemon exits with
reason=consecutive_4xx_errors. With the TFE tunnel ping, a normal read-timeout is not counted as a 4xx (it is treated as success), so this branch is now reached only by genuine HTTP errors such as a 404 for a deleted/expired assignment.
- Explicit:
- Backend Sync: Implement a way to reconcile the local
sessions.jsonwith the output ofcolab sessions. - Resource Usage: Add real-time resource usage (CPU/RAM/GPU) to the
statusoutput by executing a diagnostic snippet on the VM.
- Authentication: Uses
google-auth-oauthlibto perform a local server OAuth flow. - Global Flags:
-c,--client-oauth-config: Path to the client secrets JSON file (default:~/.colab-cli-oauth-config.json).--config: Path to the session state JSON file (default:~/.config/colab-cli/sessions.json).
- Token Storage: Credentials are persisted to
~/.config/colab-cli/token.jsonafter the initial flow. - Use
requestsfor robust HTTP interactions andpydanticfor schema validation. - Handle authentication headers (likely
Authorization: Bearer <token>or cookies).
- Stores:
StateStore(sessions.json) andSettingsStore(settings.json) both derive from_LockedFileStore, which guards concurrent access across independentcolabinvocations and the detached keep-alive daemon. - Cross-platform locking: Locking uses the
filelocklibrary rather thanfcntl.flock.fcntlis POSIX-only and is unavailable on Windows, so the original implementation crashed on import there.filelockprovides the same advisory cross-process locking on Linux, macOS, and Windows. - Shared vs. exclusive: Each store owns a
filelock.ReadWriteLockbound to a sidecar file (<path>.lock). Reads acquireread_lock()(shared — multiple concurrent readers allowed) and writes acquirewrite_lock()(exclusive). This preserves theLOCK_SH/LOCK_EXdistinction of the previousfcntlimplementation. is_singleton=False: TheReadWriteLockis created withis_singleton=False. Withfilelock's default (True), twoReadWriteLockobjects for the same path within a single process are deduplicated into one reentrant lock; its reentrancy guard then raisesRuntimeErrorwhen two threads each construct their ownStateStoreand contend for the write lock. Disabling the singleton registry makes each store's lock independent so they serialize via the underlying file lock instead.
TDD is mandatory for all session management features.
- Test Case: Verify
colab newcorrectly parses aPostAssignmentResponseand stores it in the localStateStore. - Test Case: Verify
colab stopsends aPOSTrequest with the correct XSRF token to the unassign endpoint. - Test Case: Verify that the path provided via
-cis correctly passed to the authentication flow. - Mocking: Use
unittest.mockto interceptrequests.Session.requestand return simulated XSSI-prefixed JSON payloads matching the HAR traces.
- Test Case: Verify
StateStorecorrectly handles file locking and multiple concurrent reads/writes. - Test Case: Verify
--configoverride correctly directs all operations to the specified file path. - Test Case (cross-platform locking): Verify the store locks via
filelock.ReadWriteLockon the<path>.locksidecar and does not import the POSIX-onlyfcntl. - Test Case (shared/exclusive semantics): Verify reads go through
read_lock()and writes throughwrite_lock(). - Test Case (cross-process exclusion): Hold the write lock from a separate process and confirm the store's in-process write blocks until release.
- Test Case (concurrent readers): Hold a read lock from a separate process and confirm the store can still complete a read concurrently.
- Test Case (multi-thread regression): Two
StateStoreinstances writing from different threads must serialize without raisingRuntimeError(guards theis_singleton=Falsechoice).