Three Devin Review findings on PR #173 addressed in one commit since
they're in adjacent code paths:
1. cli/commands/init.py:99 (\u{1F534}): `agnes init --token NEW` ran
step 2 verify against the OLD on-disk token because `get_token()`
read `~/.config/agnes/token.json` before the env var, and
`_override_server_env` only set the env var. So `agnes init --force`
on a machine with a stale token.json failed 401 with a confusing
'token expired' even though the --token arg was valid.
Fix: ContextVar-based override in `cli.config._token_override`
checked by `get_token()` BEFORE the on-disk read.
`_with_token_override` context manager scopes the override.
`_override_server_env` now also sets the contextvar via
`_with_token_override(token)`, so both env var and contextvar
carry the override (env for back-compat with anything bypassing
get_token; contextvar is the authoritative source).
Async-safe (each task sees its own override) and leak-proof
(resets on context exit).
2 new tests: regression on stale-disk-token + scope leak guard.
2. cli/commands/status.py:43 (\u{1F7E1}): sessions_pending_upload only
checked legacy `<workspace>/user/sessions/` and always reported 0
in workspaces bootstrapped with `agnes init` (Claude Code writes
to `~/.claude/projects/`, not the legacy path). Same bug we fixed
for `agnes push` in 08e49591.
Fix: route through `cli.lib.claude_sessions.list_session_files()`
so status and push agree on what counts as a pending session.
3. connectors/bigquery/extractor.py:111 (\u{1F7E1}): docstring claimed
"a live holder still wins the second flock attempt" — incorrect on
Linux. After `unlink()` + `open()`, the new file is a new inode;
fcntl.flock keys per-inode, so the old holder's lock does NOT block
the new acquisition. In a genuine TTL-overrun scenario two writers
CAN race the parquet.tmp.
Fix: documentation only. Comment now honestly describes the
inode-recreation behavior, names the threading.Lock as the actual
in-process guard, and flags pid-gating as the next-iteration fix
if real corruption surfaces. The 24h default TTL is well above
typical COPY durations so the practical risk is low.
Tests: 17/17 across test_cli_init.py + test_lib_pull.py + the broader
regression set.
70 lines
2.3 KiB
Python
70 lines
2.3 KiB
Python
"""`agnes status` — workspace status: initialized? data fresh? hooks active?
|
|
|
|
Server-health checks live under `agnes diagnose system` (see the
|
|
`agnes diagnose` group).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
import typer
|
|
|
|
|
|
_INIT_MARKER = "AI Data Analyst"
|
|
|
|
|
|
status_app = typer.Typer(help="Show workspace status (initialized? data fresh? hooks active?)")
|
|
|
|
|
|
@status_app.callback(invoke_without_command=True)
|
|
def status(
|
|
as_json: bool = typer.Option(False, "--json", help="Machine-readable output"),
|
|
):
|
|
workspace = Path(os.environ.get("AGNES_LOCAL_DIR", ".")).resolve()
|
|
|
|
initialized = False
|
|
claude_md = workspace / "CLAUDE.md"
|
|
if claude_md.exists():
|
|
initialized = _INIT_MARKER in claude_md.read_text(encoding="utf-8")
|
|
|
|
parquet_dir = workspace / "server" / "parquet"
|
|
parquets = list(parquet_dir.glob("*.parquet")) if parquet_dir.exists() else []
|
|
|
|
db_path = workspace / "user" / "duckdb" / "analytics.duckdb"
|
|
last_synced = None
|
|
if db_path.exists():
|
|
last_synced = datetime.fromtimestamp(db_path.stat().st_mtime, tz=timezone.utc).isoformat()
|
|
|
|
# Sessions live in ~/.claude/projects/<encoded-cwd>/ (where Claude Code
|
|
# writes them), with `<workspace>/user/sessions/` as a legacy fallback.
|
|
# The helper unions both — same source of truth as `agnes push`.
|
|
from cli.lib.claude_sessions import list_session_files
|
|
session_count = len(list_session_files(workspace))
|
|
|
|
info = {
|
|
"workspace": str(workspace),
|
|
"initialized": initialized,
|
|
"parquet_tables": len(parquets),
|
|
"duckdb_exists": db_path.exists(),
|
|
"last_synced": last_synced,
|
|
"sessions_pending_upload": session_count,
|
|
}
|
|
|
|
if as_json:
|
|
typer.echo(json.dumps(info, indent=2))
|
|
return
|
|
|
|
typer.echo(f"Workspace : {workspace}")
|
|
typer.echo(f"Initialized: {'yes' if initialized else 'no'}")
|
|
typer.echo(f"Parquets : {info['parquet_tables']}")
|
|
typer.echo(f"DuckDB : {'yes' if info['duckdb_exists'] else 'no'}")
|
|
typer.echo(f"Last sync : {last_synced or 'never'}")
|
|
typer.echo(f"Pending uploads: {session_count} sessions")
|
|
|
|
if not initialized:
|
|
typer.echo("")
|
|
typer.echo("Run `agnes init --server-url <URL> --token <PAT>` to bootstrap.")
|