Compare commits

...

3 Commits

Author SHA1 Message Date
SabhyaC26 1639a18c95 fix(devex): add OMNIGENT_SKIP_LOCK_STALENESS escape hatch
Git does not guarantee pyproject/uv.lock mtime order; name the skip
env var in the failure message so a false positive cannot hard-block
ensure/lint with no visible way out.

Signed-off-by: SabhyaC26 <sabhyachhabria@gmail.com>
2026-07-27 22:09:04 +00:00
SabhyaC26 5847181b15 fix(devex): fail ensure on stale lock; clarify proxy root cause
Add a pyproject.toml-vs-uv.lock mtime gate so --frozen cannot silently
install a stale env, document the ~/.config/uv proxy as the real cause,
and make --no-sync failures point at just ensure.

Signed-off-by: SabhyaC26 <sabhyachhabria@gmail.com>
2026-07-27 22:06:45 +00:00
SabhyaC26 472e28f00d fix(devex): stop just ensure from rewriting uv.lock and uninstalling extras
Use frozen+inexact sync, gate iOS on macOS/Bundler, and make normalize-locks
fail on real errors instead of swallowing them with || true.

Signed-off-by: SabhyaC26 <sabhyachhabria@gmail.com>
2026-07-27 21:58:41 +00:00
5 changed files with 186 additions and 13 deletions
+2 -1
View File
@@ -13,7 +13,8 @@ commit lands clean — CI runs the same checks.
Use `just` for common tasks; run `just --list` for grouped recipes.
- `just ensure` — install/check prerequisites
- `just ensure` — install/check prerequisites (`uv sync --frozen`)
- `just relock` — intentionally re-resolve and rewrite `uv.lock`
- `just run-ios` / `just run-android` — build/run mobile apps
- `just dev` / `just dev-mobile` — start the omnigent dev pod
- `just electron-dev` / `just electron-build` — Electron desktop shell
+144 -5
View File
@@ -12,15 +12,130 @@ _check-uv:
uv run --no-sync ruff --version
uv run --no-sync pre-commit --version
# Sync from the committed lockfile without rewriting it.
#
# Root cause (read this before "fixing" the flag back to --locked):
# Many Databricks developers have
# index-url = "https://pypi-proxy.cloud.databricks.com/simple"
# in ~/.config/uv/uv.toml. Any uv sync/lock that re-resolves then rewrites
# every registry URL in uv.lock to that proxy. `--locked` / `uv lock --check`
# then treat a clean pypi.org lock as stale — even when pyproject.toml is
# unchanged — which is why CI (UV_INDEX_URL=pypi.org, no user config) passes
# while local `--locked` fails. Forcing UV_INDEX_URL=pypi.org locally fixes
# the check but breaks machines where pypi.org DNS is unreachable (proxy-only
# networks still need the proxy for build-system.requires fetches).
#
# So we use `--frozen` (install pins, never touch the lock) plus an explicit
# pyproject.toml-vs-uv.lock mtime staleness gate below (skip with
# OMNIGENT_SKIP_LOCK_STALENESS=1 when git left misleading mtimes). CI remains
# the `--locked` freshness gate. Pinning `index-url` in the repo's uv.toml
# would make local and CI agree on resolution, but is deferred: it breaks
# proxy-only installs until those networks can reach pypi.org or a
# transparent mirror — see the PR for the owner decision.
#
# `--inexact` keeps optional harness extras (cursor / copilot / antigravity)
# that `omnigent setup` may have installed; `--extra all` only adds
# databricks-sdk, not those.
_ensure-uv:
uv sync --extra all --extra dev
#!/usr/bin/env bash
set -euo pipefail
if [[ ! -f uv.lock ]]; then
echo "error: uv.lock is missing. Run: just relock && just normalize-locks" >&2
exit 1
fi
# Fail if the manifest is newer than the lock — `--frozen` would otherwise
# silently install a stale environment after a pyproject.toml edit.
# Git does not guarantee relative mtimes across checkout/stash/rebase, so
# a false positive can block ensure/lint; skip with the env var below.
if [[ "${OMNIGENT_SKIP_LOCK_STALENESS:-}" != "1" ]] && [[ pyproject.toml -nt uv.lock ]]; then
echo "error: pyproject.toml is newer than uv.lock." >&2
echo " This check asserts the lock is at least as new as the manifest" >&2
echo " so \`uv sync --frozen\` does not silently install a stale env." >&2
echo " Options:" >&2
echo " 1. Re-resolve: just relock && just normalize-locks" >&2
echo " 2. Skip gate: OMNIGENT_SKIP_LOCK_STALENESS=1 just ensure" >&2
echo " (use when git left misleading mtimes but the lock is valid)" >&2
echo " 3. Then re-run: just ensure" >&2
exit 1
fi
set +e
uv sync --frozen --inexact --extra all --extra dev
status=$?
set -e
if [[ "${status}" -eq 0 ]]; then
exit 0
fi
echo "" >&2
echo "error: \`uv sync --frozen\` failed (exit ${status})." >&2
echo " Recovery:" >&2
echo " • Lock rewritten by an older \`just ensure\` (proxy URLs)?" >&2
echo " git checkout -- uv.lock && just normalize-locks" >&2
echo " • pyproject.toml changed and the lock needs re-resolving?" >&2
echo " just relock && just normalize-locks" >&2
echo " Then re-run: just ensure" >&2
exit "${status}"
# Intentional re-resolve (updates uv.lock). Day-to-day setup uses
# `_ensure-uv` / `just ensure` with `--frozen` instead.
[group('setup')]
relock:
uv sync --inexact --extra all --extra dev
@echo "uv.lock may point at your local index; run \`just normalize-locks\` before committing."
# Run a command via the project venv without re-resolving (which would
# rewrite uv.lock under a corporate proxy). If the venv is missing or
# empty, fail with a pointer to ensure instead of a raw spawn /
# ModuleNotFoundError.
_uv-run-no-sync +args:
#!/usr/bin/env bash
set -euo pipefail
if [[ ! -x .venv/bin/python ]]; then
echo "error: project .venv is missing or incomplete." >&2
echo " Fix: just ensure # syncs --extra all --extra dev from uv.lock" >&2
exit 1
fi
set +e
uv run --no-sync {{ args }}
status=$?
set -e
if [[ "${status}" -eq 0 ]]; then
exit 0
fi
# Empty venv from `uv run --no-sync` after a deleted .venv: spawn fails.
if [[ ! -x .venv/bin/pre-commit ]] && [[ " {{ args }} " == *" pre-commit "* ]]; then
echo "" >&2
echo "error: pre-commit is not installed in .venv (dev extra missing?)." >&2
echo " Fix: just ensure" >&2
exit 1
fi
exit "${status}"
# --- iOS Ruby dependencies ---
_check-ios:
#!/usr/bin/env bash
set -euo pipefail
if [[ "$(uname -s)" != "Darwin" ]]; then
echo "Skipping iOS check (not macOS)."
exit 0
fi
if ! command -v bundle >/dev/null 2>&1; then
echo "Skipping iOS check (Bundler not found)."
exit 0
fi
cd web/ios && bundle check
_ensure-ios:
#!/usr/bin/env bash
set -euo pipefail
if [[ "$(uname -s)" != "Darwin" ]]; then
echo "Skipping iOS setup (not macOS)."
exit 0
fi
if ! command -v bundle >/dev/null 2>&1; then
echo "Skipping iOS setup (Bundler not found)."
exit 0
fi
cd web/ios && (bundle check || bundle install)
# --- omnidev Rust dev tool ---
@@ -86,15 +201,39 @@ electron-build: _ensure-web _ensure-electron
[group('lint')]
lint: _ensure-uv
uv run pre-commit run
just _uv-run-no-sync pre-commit run
[group('lint')]
lint-all: _ensure-uv
uv run pre-commit run --all-files
just _uv-run-no-sync pre-commit run --all-files
# --- Lockfile maintenance ---
# Fixers exit 1 when they rewrite (pre-commit convention). Treat that as
# success here; real errors use exit code 2+ from the scripts.
# Always `--no-sync`: a bare `uv run` would re-resolve against the local
# index and rewrite uv.lock (undoing `--frozen` in `_ensure-uv`).
[group('lint')]
normalize-locks: _ensure-uv
uv run scripts/normalize_package_lock_registry.py web/package-lock.json web/electron/package-lock.json editors/vscode/package-lock.json || true
uv run scripts/normalize_uv_lock_registry.py uv.lock || true
#!/usr/bin/env bash
set -euo pipefail
run_fixer() {
local ec=0
local out
out="$(uv run --no-sync "$@" 2>&1)" || ec=$?
printf '%s\n' "${out}"
if [[ "${ec}" -eq 0 || "${ec}" -eq 1 ]]; then
return 0
fi
if [[ "${out}" == *"No module named"* ]] \
|| [[ "${out}" == *"Failed to spawn"* ]] \
|| [[ "${out}" == *"No such file or directory"* ]]; then
echo "error: project .venv is missing tools needed for normalize-locks." >&2
echo " Fix: just ensure" >&2
return 1
fi
return "${ec}"
}
run_fixer scripts/normalize_package_lock_registry.py \
web/package-lock.json web/electron/package-lock.json editors/vscode/package-lock.json
run_fixer scripts/normalize_uv_lock_registry.py uv.lock
+20 -4
View File
@@ -78,7 +78,8 @@ def main(argv: list[str]) -> int:
:returns: In fix mode, ``1`` when a file was modified (so the commit
aborts and the change is re-staged) else ``0``. In ``--check``
mode, ``1`` when any file is not already canonical (printing the
offending URLs) else ``0``; no file is written.
offending URLs) else ``0``; no file is written. Missing or
unreadable files (and invalid JSON after rewrite) return ``2``.
"""
check = "--check" in argv
files = [a for a in argv if a != "--check"]
@@ -86,7 +87,12 @@ def main(argv: list[str]) -> int:
if check:
ok = True
for name in files:
offenders = non_canonical_urls(Path(name).read_text())
try:
text = Path(name).read_text()
except OSError as exc:
print(f"error: {name}: {exc}", file=sys.stderr)
return 2
offenders = non_canonical_urls(text)
if offenders:
ok = False
unique = sorted(set(offenders))
@@ -105,11 +111,21 @@ def main(argv: list[str]) -> int:
changed = False
for name in files:
path = Path(name)
original = path.read_text()
try:
original = path.read_text()
except OSError as exc:
print(f"error: {name}: {exc}", file=sys.stderr)
return 2
normalized = normalize_text(original)
if normalized != original:
# Validate that the result is still valid JSON before writing.
json.loads(normalized)
try:
json.loads(normalized)
except json.JSONDecodeError as exc:
print(
f"error: {name}: normalized result is not valid JSON: {exc}", file=sys.stderr
)
return 2
path.write_text(normalized)
count = len(non_canonical_urls(original))
print(f"{name}: normalized {count} resolved URL(s) to {_CANONICAL_REGISTRY}")
+13 -3
View File
@@ -89,7 +89,8 @@ def main(argv: list[str]) -> int:
:returns: In fix mode, ``1`` when a file was modified (so the commit
aborts and the change is re-staged) else ``0``. In ``--check``
mode, ``1`` when any file is not already canonical (printing the
offending URLs) else ``0``; no file is written.
offending URLs) else ``0``; no file is written. Missing or
unreadable files return ``2`` (distinct from the rewrite signal).
"""
check = "--check" in argv
files = [a for a in argv if a != "--check"]
@@ -97,7 +98,12 @@ def main(argv: list[str]) -> int:
if check:
ok = True
for name in files:
offenders = non_canonical_registries(Path(name).read_text())
try:
text = Path(name).read_text()
except OSError as exc:
print(f"error: {name}: {exc}", file=sys.stderr)
return 2
offenders = non_canonical_registries(text)
if offenders:
ok = False
unique = sorted(set(offenders))
@@ -114,7 +120,11 @@ def main(argv: list[str]) -> int:
changed = False
for name in files:
path = Path(name)
original = path.read_text()
try:
original = path.read_text()
except OSError as exc:
print(f"error: {name}: {exc}", file=sys.stderr)
return 2
normalized = normalize_text(original)
if normalized != original:
path.write_text(normalized)
+7
View File
@@ -166,3 +166,10 @@ def test_main_check_flag_position_independent(tmp_path: Path) -> None:
lock = tmp_path / "uv.lock"
lock.write_text(f'source = {{ registry = "{_CANONICAL}" }}\n')
assert _MOD.main([str(lock), "--check"]) == 0
def test_main_missing_file_returns_two(tmp_path: Path) -> None:
"""A missing lockfile is a real error (exit 2), not a rewrite signal."""
missing = tmp_path / "does-not-exist.lock"
assert _MOD.main([str(missing)]) == 2
assert _MOD.main(["--check", str(missing)]) == 2