From 12d0d3e9971d0c6eb4f9862a4a8b69c167a3a0f1 Mon Sep 17 00:00:00 2001 From: rpriven Date: Thu, 28 May 2026 19:46:57 -0600 Subject: [PATCH] feat(statusline): honor user-configured location over IP geolocation (#843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The statusline determined location/weather purely via ip-api.com IP geolocation, returning the wrong city for VPN/proxy/corporate-network users whose exit node differs from their actual location — and falling back to hardcoded San Francisco coords. settings.json already has a `.location` object (city/regionName/lat/lon) but the statusline ignored it. This makes settings.json `.location` authoritative when present: - Location display uses the configured city/region - Weather uses the configured lat/lon - Optional `.location.countryCode` drives the flag emoji (falls back to 🌐) - ip-api.com is skipped entirely when location is configured (correctness for VPN users + one fewer third-party request exposing the user's IP) - IP geolocation remains the fallback for unconfigured users The `.location.*` reads are guarded with `?` so a malformed user-edited location can't break the rest of the settings cache. Tested end-to-end on Debian 13 / PAI v5.0.0: configured (with + without countryCode), unconfigured ip-api fallback, partial (city-only → weather falls back gracefully), and malformed-location paths. --- .../v5.0.0/.claude/PAI/statusline-command.sh | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Releases/v5.0.0/.claude/PAI/statusline-command.sh b/Releases/v5.0.0/.claude/PAI/statusline-command.sh index 68bce90cf..7f5e1ab31 100755 --- a/Releases/v5.0.0/.claude/PAI/statusline-command.sh +++ b/Releases/v5.0.0/.claude/PAI/statusline-command.sh @@ -43,7 +43,12 @@ else "work_count=" + (.counts.work // 0 | tostring) + "\n" + "sessions_count=" + (.counts.sessions // 0 | tostring) + "\n" + "research_count=" + (.counts.research // 0 | tostring) + "\n" + - "ratings_count=" + (.counts.ratings // 0 | tostring) + "ratings_count=" + (.counts.ratings // 0 | tostring) + "\n" + + "LOC_CITY=" + (.location.city? // "" | @sh) + "\n" + + "LOC_REGION=" + (.location.regionName? // "" | @sh) + "\n" + + "LOC_LAT=" + (.location.lat? // "" | tostring | @sh) + "\n" + + "LOC_LON=" + (.location.lon? // "" | tostring | @sh) + "\n" + + "LOC_CC=" + (.location.countryCode? // "" | @sh) ' "$SETTINGS_FILE" 2>/dev/null > "$_SETTINGS_CACHE" # shellcheck disable=SC1090 source "$_SETTINGS_CACHE" @@ -531,7 +536,19 @@ if [ "$MODE" = "mini" ] || [ "$MODE" = "normal" ]; then cache_age=999999 [ -f "$LOCATION_CACHE" ] && cache_age=$((NOW_EPOCH - $(get_mtime "$LOCATION_CACHE"))) - if [ "$cache_age" -gt "$LOCATION_CACHE_TTL" ]; then + if [ -n "${LOC_CITY:-}" ]; then + # User-configured location (settings.json .location) is authoritative. + # Skip ip-api.com entirely — correct for VPN/proxy/corporate-network users + # whose exit node is in a different city, AND one fewer third-party request + # exposing the user's IP. IP geolocation remains the fallback when unset. (#843) + jq -n --arg city "$LOC_CITY" --arg region "${LOC_REGION:-}" \ + --arg cc "${LOC_CC:-}" \ + --arg lat "${LOC_LAT:-}" --arg lon "${LOC_LON:-}" \ + '{city:$city, regionName:$region, countryCode:$cc, + lat:($lat | if . == "" then null else tonumber end), + lon:($lon | if . == "" then null else tonumber end)}' \ + > "$LOCATION_CACHE" 2>/dev/null + elif [ "$cache_age" -gt "$LOCATION_CACHE_TTL" ]; then loc_data=$(curl -s --max-time 2 "http://ip-api.com/json/?fields=city,region,regionName,country,countryCode,lat,lon" 2>/dev/null) if [ -n "$loc_data" ] && echo "$loc_data" | jq -e '.city' >/dev/null 2>&1; then echo "$loc_data" > "$LOCATION_CACHE"