diff --git a/.github/workflows/evals-periodic.yml b/.github/workflows/evals-periodic.yml index 2cfcfdb2ed..0275333010 100644 --- a/.github/workflows/evals-periodic.yml +++ b/.github/workflows/evals-periodic.yml @@ -98,6 +98,7 @@ jobs: contents: read outputs: periodic_slices: ${{ steps.periodic-matrix.outputs.slices }} + periodic_codex_slices: ${{ steps.periodic-matrix.outputs.codex_slices }} periodic_timeout_minutes: ${{ steps.periodic-matrix.outputs.timeout_minutes }} gate_slices: ${{ steps.gate-matrix.outputs.slices }} gate_timeout_minutes: ${{ steps.gate-matrix.outputs.timeout_minutes }} @@ -113,13 +114,18 @@ jobs: - name: Emit run manifest (ALL periodic tests minus reasoned excludes) env: EVALS_ALL: "1" - run: EVALS_TIER=periodic bun --no-install run scripts/test-paid-shards.ts --tier periodic --emit-plan /tmp/paid-plan/manifest.json --slice-budget 540 --jobs 2 --max-parallel 25 + run: EVALS_TIER=periodic bun --no-install run scripts/test-paid-shards.ts --tier periodic --emit-plan /tmp/paid-plan/manifest.json --slice-budget 540 --jobs 2 --max-parallel 26 + # Codex-only slices run on the host-run eval-codex-slices job; the + # container executor runs every other slice. - name: Derive the periodic executor matrix from the plan id: periodic-matrix run: | - echo "slices=$(jq -c '[range(1; .sliceCount + 1)]' /tmp/paid-plan/manifest.json)" >> "$GITHUB_OUTPUT" - echo "timeout_minutes=$(jq -e '.plan.ciTimeoutMinutes' /tmp/paid-plan/manifest.json)" >> "$GITHUB_OUTPUT" + { + echo "slices=$(jq -c '[range(1; .sliceCount + 1)] - .plan.codexSlices' /tmp/paid-plan/manifest.json)" + echo "codex_slices=$(jq -c '.plan.codexSlices' /tmp/paid-plan/manifest.json)" + echo "timeout_minutes=$(jq -e '.plan.ciTimeoutMinutes' /tmp/paid-plan/manifest.json)" + } >> "$GITHUB_OUTPUT" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: @@ -166,7 +172,7 @@ jobs: fail-fast: false # Every planned slice starts at once; test/evals-workflow-wiring.test.ts # fails when the live plan outgrows this cap. - max-parallel: 25 + max-parallel: 26 matrix: slice: ${{ fromJSON(needs.plan-slices.outputs.periodic_slices) }} steps: @@ -280,6 +286,135 @@ jobs: if-no-files-found: ignore retention-days: 30 + # Codex shards on the host. Codex runs every shell command inside a + # bubblewrap sandbox, which needs an unprivileged user namespace. A job + # container cannot get one: Docker's default seccomp and AppArmor profiles + # block it, and Ubuntu 24.04 hosts set + # kernel.apparmor_restrict_unprivileged_userns=1, which no container option + # can change. This job runs on the runner VM itself (ephemeral, one job), + # lifts that host restriction, and runs the same CI image with both + # profiles unconfined. Only the planner's Codex slices run here + # (scripts/lib/paid-plan.ts codexSlicesOf); slice artifacts use the same + # names as eval-slices, so the report reconciles one manifest. + eval-codex-slices: + runs-on: ubicloud-standard-8 + needs: [build-image, plan-slices] + if: ${{ needs.plan-slices.outputs.periodic_codex_slices != '[]' }} + env: + EVALS_RUN_ID: ci-${{ github.run_id }}-${{ github.run_attempt }}-eval-slices-${{ matrix.slice }} + timeout-minutes: ${{ fromJSON(needs.plan-slices.outputs.periodic_timeout_minutes) }} + permissions: + contents: read + packages: read + strategy: + fail-fast: false + max-parallel: 26 + matrix: + slice: ${{ fromJSON(needs.plan-slices.outputs.periodic_codex_slices) }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: paid-plan + path: ${{ runner.temp }}/paid-plan + + - name: Let Codex's bubblewrap sandbox create user namespaces + run: | + if [ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]; then + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + fi + + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # The same steps as eval-slices (bun temp, deps, build, Codex login, + # slice run) inside the CI image. The container's home is a fresh + # runner-temp directory, so logs and captures upload from there. + - name: Run periodic Codex slice ${{ matrix.slice }} + shell: bash + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + EVAL_IMAGE: ${{ needs.build-image.outputs.image-tag }} + SLICE: ${{ matrix.slice }} + EVALS_JOBS: "2" + run: | + mkdir -p "$RUNNER_TEMP/eval-home" "$RUNNER_TEMP/paid-slice-results" + docker run --rm --user "$(id -u):$(id -g)" \ + --security-opt seccomp=unconfined --security-opt apparmor=unconfined \ + -e HOME=/home/runner -e ANTHROPIC_API_KEY -e OPENAI_API_KEY -e GEMINI_API_KEY -e SLICE -e EVALS_JOBS \ + -e EVALS_RUN_ID -e EVALS_PROFILE -e EVALS_FRESH -e EVALS_CACHE_PURPOSE -e CI -e GITHUB_ACTIONS \ + -e GITHUB_RUN_ID -e GITHUB_RUN_ATTEMPT -e GITHUB_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REPOSITORY \ + -e GITHUB_EVENT_NAME -e GITHUB_BASE_REF -e GITHUB_HEAD_REF \ + -e PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers -e EVALS_CONCURRENCY=2 -e GSTACK_EVAL_DIR=/tmp/paid-slice-results \ + -v "$GITHUB_WORKSPACE:/workspace/gstack" -w /workspace/gstack \ + -v "$RUNNER_TEMP/eval-home:/home/runner" \ + -v "$RUNNER_TEMP/paid-plan:/tmp/paid-plan:ro" \ + -v "$RUNNER_TEMP/paid-slice-results:/tmp/paid-slice-results" \ + "$EVAL_IMAGE" bash -c ' + set -euo pipefail + mkdir -p "$HOME/.cache/bun" + export BUN_INSTALL_CACHE_DIR="$HOME/.cache/bun" BUN_TMPDIR="$HOME/.cache/bun" TMPDIR="$HOME/.cache" + if diff -q /opt/node_modules_cache/.bun.lock bun.lock >/dev/null 2>&1; then + rm -rf node_modules && cp -r /opt/node_modules_cache node_modules + else + bun install --frozen-lockfile + fi + bun run build + codex="${GSTACK_CI_CODEX_BIN_DIR:?}/codex" + export CODEX_HOME="$HOME/.gstack-ci-codex" + mkdir -p "$CODEX_HOME" + if [ -n "${OPENAI_API_KEY:-}" ]; then + printenv OPENAI_API_KEY | "$codex" login --with-api-key + "$codex" login status >/dev/null 2>&1 + else + echo "::warning::OPENAI_API_KEY is unavailable; Codex eval cases cannot authenticate and will fail." + fi + export GSTACK_CI_CODEX_HOME="$CODEX_HOME" + unset CODEX_HOME + EVALS_TIER=periodic bun run scripts/test-paid-shards.ts --tier periodic --plan /tmp/paid-plan/manifest.json --slice "$SLICE" + ' + + - name: Upload slice results + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: paid-slice-${{ matrix.slice }}-a${{ github.run_attempt }} + path: ${{ runner.temp }}/paid-slice-results + retention-days: 90 + + - name: Upload native capture evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: native-captures-${{ env.EVALS_RUN_ID }} + include-hidden-files: true + path: | + ${{ runner.temp }}/eval-home/.gstack/projects/*/e2e-runs + ${{ runner.temp }}/eval-home/.gstack/projects/*/evals/qa-callers + ${{ runner.temp }}/eval-home/.gstack-dev/e2e-runs + ${{ runner.temp }}/eval-home/.gstack-dev/evals/qa-callers + if-no-files-found: ignore + retention-days: 90 + + - name: Upload shard logs + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: paid-logs-slice-${{ matrix.slice }}-a${{ github.run_attempt }} + include-hidden-files: true + path: ${{ runner.temp }}/eval-home/.cache/gstack-paid-shard-*.log + if-no-files-found: ignore + retention-days: 30 + # Weekly EVALS_ALL gate-tier census: PR lanes are diff-billed, so without # this the full gate census might never execute anywhere and the selector's # blind spots rot invisibly. Census health, not selector correctness — @@ -389,7 +524,7 @@ jobs: report: runs-on: ubicloud-standard-2 - needs: [plan-slices, eval-slices, gate-census] + needs: [plan-slices, eval-slices, eval-codex-slices, gate-census] # !cancelled(): the report must run (and FAIL) when an executor died — a # missing slice artifact reading as green is the class this lane kills — # but a cancelled run stops here. @@ -521,7 +656,7 @@ jobs: # issue (never a new issue per week) with the headline and failure block # of both lanes, and close it on the next green run. - name: Upsert tracking issue on failure - if: always() && (steps.reconcile.outputs.exit != '0' || steps.gate-reconcile.outputs.exit != '0' || steps.pass-rates.outputs.exit != '0' || needs.eval-slices.result != 'success' || needs.gate-census.result != 'success') + if: always() && (steps.reconcile.outputs.exit != '0' || steps.gate-reconcile.outputs.exit != '0' || steps.pass-rates.outputs.exit != '0' || needs.eval-slices.result != 'success' || !contains(fromJSON('["success","skipped"]'), needs.eval-codex-slices.result) || needs.gate-census.result != 'success') env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REDISPATCH: ${{ steps.verdict.outputs.redispatch }} @@ -536,7 +671,7 @@ jobs: if [ -n "$REDISPATCH_OF" ]; then echo; echo "This run is the one INFRA/INCOMPLETE re-dispatch of run ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${REDISPATCH_OF}; both runs are reported."; fi if [ "$REDISPATCH" = "true" ]; then echo; echo "Every red verdict is machine-classified INFRA/INCOMPLETE: re-dispatching once as a new run (EVAL_POLICY.infraRedispatch). This run stays red and reported."; fi echo - echo "- periodic reconciliation exit: ${{ steps.reconcile.outputs.exit }} (slices job: ${{ needs.eval-slices.result }})" + echo "- periodic reconciliation exit: ${{ steps.reconcile.outputs.exit }} (slices job: ${{ needs.eval-slices.result }}, Codex slices job: ${{ needs.eval-codex-slices.result }})" echo "- gate census reconciliation exit: ${{ steps.gate-reconcile.outputs.exit }} (census job: ${{ needs.gate-census.result }})" echo "- pass-rate history gate exit: ${{ steps.pass-rates.outputs.exit }}" echo @@ -573,7 +708,7 @@ jobs: fi - name: Close the tracking issue on a green run - if: always() && steps.reconcile.outputs.exit == '0' && steps.gate-reconcile.outputs.exit == '0' && steps.pass-rates.outputs.exit == '0' && needs.eval-slices.result == 'success' && needs.gate-census.result == 'success' + if: always() && steps.reconcile.outputs.exit == '0' && steps.gate-reconcile.outputs.exit == '0' && steps.pass-rates.outputs.exit == '0' && needs.eval-slices.result == 'success' && contains(fromJSON('["success","skipped"]'), needs.eval-codex-slices.result) && needs.gate-census.result == 'success' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REDISPATCH_OF: ${{ inputs.redispatch_of }} @@ -588,7 +723,7 @@ jobs: fi - name: Fail the workflow when reconciliation failed - if: always() && (steps.reconcile.outputs.exit != '0' || steps.gate-reconcile.outputs.exit != '0' || steps.pass-rates.outputs.exit != '0' || needs.eval-slices.result != 'success' || needs.gate-census.result != 'success') + if: always() && (steps.reconcile.outputs.exit != '0' || steps.gate-reconcile.outputs.exit != '0' || steps.pass-rates.outputs.exit != '0' || needs.eval-slices.result != 'success' || !contains(fromJSON('["success","skipped"]'), needs.eval-codex-slices.result) || needs.gate-census.result != 'success') run: exit 1 # The one INFRA/INCOMPLETE re-dispatch (UC-E1). Its own job so the report diff --git a/AGENTS.md b/AGENTS.md index e76cf48207..6c384460e9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -289,5 +289,7 @@ bun run skill:check # health dashboard for all skills - Browse daemon HTTP routes are entries in `browse/src/routes/table.ts` (its header shows how to add one); never dispatch on `url.pathname` in `server.ts`. - Both test lanes run shards through `scripts/lib/shard-engine.ts`; the free and paid runners hold lane policy only. PTY harness code lives in `test/helpers/pty/*` behind the `claude-pty-runner.ts` barrel. - Outside-voice failure prose (auth, timeout, empty, fallback) comes only from `outsideVoiceFailurePolicy()` in `scripts/resolvers/outside-voice.ts`. +- Every env-var-host fence starts with the shared prelude from `scripts/resolvers/runtime-root.ts` (inserted by its post-render pass); never resolve gstack's root by hand in a template. +- Every Codex run's verdict goes through `lib/outside-review-result.ts`; its reason codes live in `lib/gate-outcomes.ts`, each with a `docs/troubleshooting.md` anchor. - `test/module-size-ratchet.test.ts` keeps refactored owner modules at or under 800 lines (150 per function) and residual files from growing. - The `claude` CLI binary resolves via `lib/claude-bin.ts` (re-exported from `browse/src/claude-bin.ts` for browse internals; `Bun.which()` + `GSTACK_CLAUDE_BIN` override). Set `GSTACK_CLAUDE_BIN=wsl` plus `GSTACK_CLAUDE_BIN_ARGS='["claude"]'` to run Claude through WSL on Windows. diff --git a/BROWSER.md b/BROWSER.md index 1ed56dd43b..bb511bb142 100644 --- a/BROWSER.md +++ b/BROWSER.md @@ -530,7 +530,7 @@ from `snapshot`, or `@c` refs from `snapshot -C`. Full table: | `fill ` | Fill input | | `select ` | Select dropdown option (value, label, or visible text) | | `hover ` | Hover element | -| `type ` | Type into focused element | +| `type [--selector ] [--] ` | Type into the focused element, or into `` with `--selector`; put text that starts with `--` after `--`. Bare `type` hints when its first word looks like a selector | | `press ` | Playwright keyboard key (case-sensitive: Enter, Tab, ArrowUp, Shift+Enter, Control+A, ...) | | `scroll [sel\|@ref]` | Scroll element into view, or jump to page bottom if no selector | | `viewport [] [--scale ]` | Set viewport size + optional `deviceScaleFactor` 1-3 (retina screenshots) | @@ -647,6 +647,8 @@ Storage stays intact by default. With explicit approval on a Chromium target, `- | `status` | Daemon health + mode (headless / headed / cdp) | | `stop` | Shut down daemon (succeeds even if the daemon already died — never boots one just to stop it; reaps a surviving recorded headless Chromium after identity checks) | | `restart` | Restart daemon | +| `profiles [list]` | List the per-project headed Chromium profiles: size, last use, and which browser holds each | +| `profiles prune [--days N]` | Remove headed profiles idle for N days (default 30); never one a live browser holds | | `connect` | Launch headed GStack Browser with Side Panel extension | | `disconnect` | Close headed Chrome, return to headless | | `focus [@ref]` | Bring headed Chrome to foreground (macOS); `@ref` also scrolls into view | @@ -886,6 +888,14 @@ with your tabs and bookmarks stays untouched. - **Demos** where you're sharing your screen - **Pair-agent** sessions (the remote agent drives your local browser) +Headed browse keeps one Chromium profile per project, at +`/.gstack/chromium-profile` (`CHROMIUM_PROFILE` still wins). The first +headed start copies your logins from `~/.gstack/chromium-profile` when no browser +is using it, and otherwise starts fresh and prints how to import. If a live +browser already holds the project's profile, browse names it and stops instead +of killing it. `browse profiles` lists the profiles; `browse profiles prune +--days 30` removes idle ones. + ### Aside and third-party drives (v1.72.0.0+) For third-party website moments (registering an API key, configuring a vendor diff --git a/CHANGELOG.md b/CHANGELOG.md index f73fedaa31..1baf18f1e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,111 @@ # Changelog +## [1.91.19.0] - 2026-10-03 + +**A check that did not run now says so, and memory stops losing transcripts.** +**Codex and the other non-Claude hosts find gstack in every step of every skill.** + +This release closes the severe bugs left in the tracker. Two promises lead. First, nothing is silently lost: memory ingest marks a transcript saved only after gbrain confirms it landed, and learnings that could not be written say so. Second, no false all-clear: a Codex review whose sandbox could not start, a retired Codex model, a design binary killed at launch, and a learnings search with no Bun all used to read as "ready", "pass" or "no results". Each now prints `not run` or `unavailable` with the reason and the fix command, and every one of those messages has a section in the new [docs/troubleshooting.md](docs/troubleshooting.md). + +### The numbers that matter + +Measured on this release's tests against v1.91.16.0's code (each regression test was run on both; the counts come from those runs and from `bun run gen:skill-docs --host all`). + +| Check | v1.91.16.0 | v1.91.19.0 | +|---|---|---| +| Codex review in a container with no user namespaces | gate `pass` on "no findings" | `unavailable`, missing coverage | +| Structured outside review with a `[P0]` finding | gate `pass` | gate `fail` | +| Generated bash blocks on env-var hosts that use a gstack path without finding gstack (`test/env-host-fence-conformance.test.ts`) | hundreds | 0 | +| Codex `/ship` SKILL.md | 202-241 KB, ~57K tokens | carved, ~20K tokens | +| Redaction warnings on one rendered `/diagram` (SVG + .excalidraw) | 115 | 0 | +| A transcript gbrain skipped during import | stamped as saved | retried until gbrain has it | + +The first two rows are the ones to care about: a review that read nothing, or found a P0, used to let /ship through. + +### What changes for you + +- **Gates tell the truth.** Outside reviews go through one classifier that reads the text, stderr and exit status. Codex reviews that could not run print `Codex outside review unavailable: ` with the fix, and /ship and /review show them as missing coverage, never as passed. +- **Memory keeps what you gave it.** Memory ingest checks that each page landed before marking it saved, retries pages gbrain skipped, and sets aside (and names) one page that keeps breaking a batch. After upgrading, a bounded catch-up per `/sync-gbrain` re-imports transcripts older versions lost (`gstack-memory-ingest --reconcile --dry-run` previews it). Transcripts import into a private gbrain source per repository. +- **Codex, Factory, OpenCode, Cursor, Copilot and Kiro** see the gstack router (setup installs real copies, which Codex requires), get the design and make-pdf binaries, and every skill block finds gstack on its own, honoring `CODEX_HOME` and an exported `GSTACK_ROOT`. `./setup --status` says whether each install's router is a real file and its section links resolve. +- **Pushes stop being blocked by false positives:** diagram coordinates, `session = requests.Session()`, `CLAUDE.local.md`, four-part version numbers and `postgres:postgres@localhost`. Real secrets still block. +- **Windows:** /freeze, /guard and /investigate stop denying every edit, the generators no longer crash with EEXIST, the node-server bundle builds, and the /cso helper builds with the current Windows SDK. +- **/ship in a repo without a VERSION file** ships without a version change instead of stopping. + +### Behavior changes you may notice + +- **Codex reviews in containers say "unavailable" instead of passing.** Fix: enable unprivileged user namespaces, or in a container you trust `export GSTACK_CODEX_NO_SANDBOX=1`. +- **The structured outside-review gate fails on P0 findings**, not only P1. An untagged structured review is `OUTSIDE_STATUS: unverified` (missing coverage). +- **`CODEX_MODE: unverified`** replaces `ready` when the model check times out; the review still runs and its own result is checked. +- **The first automatic Codex review on a machine prints a one-time notice** naming the provider and which login or key pays. Reviews stay on; `gstack-config set codex_reviews disabled` turns them off. +- **/ship without a version source ships without a version change:** no bump, no CHANGELOG version header, no `vX.Y.Z` title prefix. To version releases, create `VERSION` or `echo package.json > .gstack/version-path`. A pinned version file that is missing or malformed stops /ship with its path. +- **The plan completion audit says `not run`** instead of auditing your PR against the newest unrelated plan. Bind one with a `Plan: ` line in the PR body, or run /autoplan. +- **Learnings and timeline lookups report a missing Bun** (exit 127 with the fix) and skills print `LEARNINGS: unavailable ()` instead of an empty list. +- **Design skills say `DESIGN_NOT_AVAILABLE: --version exited `** when the design binary cannot start, instead of claiming it is ready. Fix: `cd && ./setup`. +- **`/sync-gbrain --dream` (and `--full`) runs only the call-graph phase.** If your gbrain cannot, it skips and says so; run `gbrain dream --source ` yourself for the full ~35-minute cycle. +- **`gstack-memory-ingest --no-write` is a true dry run** and no longer records pages as saved. +- **Transcripts with no repository stay on this machine** when your brain is remote. Staged pages carry a `gstack_content_sha256` frontmatter field. +- **Disabled skills leave the router** (`gstack-config set disabled_skills ...` updates it in the same command). +- **`browse type --selector `** types into an element. Bare `browse type` still types into the focused element and hints when the first word looks like a selector. Text starting with `--` goes after `--`. +- **Headed browse keeps one profile per project** (`/.gstack/chromium-profile`). First use copies your logins from `~/.gstack/chromium-profile` when no browser is using it. `browse profiles` lists them; `browse profiles prune --days 30` removes idle ones. If a live browser holds the profile, browse names it and stops instead of killing it. +- **A redirect or link to a link-local or metadata address resets the tab to `about:blank`** and the command fails with the reason. +- **`BROWSE_EXTENSION_ID` is ignored;** forks use `gstack-config set browse_extension_id `. `BROWSE_EXTENSIONS_DIR` runs headless with no window. +- **`$D` (design) no longer picks up `OPENAI_API_KEY` from a project's `.env`.** Export it in your shell or use `~/.gstack/openai.json`. +- **A database URL with the default `postgres`/`postgres` login still blocks a push on any host but `localhost` or `127.0.0.1`,** including a compose service name like `@db`. Use `localhost`, or read the URL from an env var. +- **Auto-update (team mode) no longer says "just upgraded" when setup failed.** It retries after 1h, 6h and 24h, and each session start prints one line naming what did not finish and the fix. +- **Hand edits to installed router copies are saved to `~/.gstack/backups/skill-copies/`, then overwritten.** Setup prints where. +- **In Conductor, setup no longer installs the AskUserQuestion preference hook** and removes one it added. To keep it: `gstack-config set plan_tune_hooks yes`, then `./setup`. +- **Required team mode also blocks Copilot CLI's skill tool.** Re-run `gstack-team-init required` to upgrade the project hook. +- **`gstack-qa-evidence capture` prints a `revalidate` list after your inputs change**, naming the probes to rerun on current inputs before `materialize`; `materialize` says its verdict is final for that report root, so rerun the listed probes first. +- **/gstack-upgrade asks you to paste the absolute path Step 2 prints**; every later step checks it is inside gstack's checkout before running git. +- **/ship's documentation gate snapshots its inputs in one call.** `gstack-docs-candidate snapshot` records the audit candidate (base, HEAD, index, changed and new paths, content hashes) and `compare` reports what changed after the child returns. +- **Two setups writing settings.json at once both land.** A writer whose lock attempt raced a holder releasing it used to skip its hook change (exit 5); it now retries once and only reports `cannot create lock` when the lock directory truly cannot be created. +- **/plan-ceo-review prints its mode handoff** by running `gstack-ceo-mode-handoff` right after the mode is chosen (by you or by a saved preference), so the selected mode and approved decisions always appear before the review continues. +- **Browser setup names the platform when Aside is absent:** the probe prints `NEEDS_ASIDE: ` (was `NEEDS_ASIDE`), and the macOS-only download pitch trusts that line instead of re-checking the OS. `GSTACK_PLATFORM` overrides it for tests and unusual hosts; set it in your shell, never a project `.env`. + +### Security + +- A repository's `.env` or `bunfig.toml` can no longer change gstack's browser. Compiled binaries and the browse daemons ignore them, so a project cannot turn off Chromium's sandbox (`GSTACK_CHROMIUM_NO_SANDBOX`) or run code through a bunfig `preload`. This needs Bun 1.3.3 or later; gstack now requires Bun 1.4.0 (`engines.bun`), the version CI runs. +- The browse sidebar terminal accepts connections only from the gstack extension. +- Browse refuses link-local and cloud-metadata addresses (169.254.0.0/16 including container credential endpoints, 100.100.100.200, fe80::/10, fc00::/7) in every numeric and IPv6-mapped spelling, including after redirects and page-driven navigation. Connection-time DNS rebinding is not covered. +- Screenshot, PDF and scrape output paths are checked the way the OS resolves them, so a symlink cannot redirect a write outside the allowed folders. +- `GSTACK_CODEX_NO_SANDBOX` is read only from your shell environment (exactly `1`), and every use prints a warning. +- The iOS QA daemon bounds each device response (one 30-second deadline for the whole response, 64 MiB cap) and never replays a tap or write whose response was lost. + +### Upgrade + +- **Claude Code:** run `/gstack-upgrade`, then start a new session. +- **Codex and the other env-var hosts (Factory, OpenCode, Cursor, Copilot, Kiro):** find your checkout with `./setup --status` (the `source` column), then run `cd && git pull && ./setup --host `, and start a new session. +- Then `./setup --status` should show each install's router as a real file with every section link resolving. + +### Itemized changes + +#### Fixed +- **Memory and gbrain.** Ingest stamped pages gbrain skipped or never stored (#2778); `--no-write` wrote state. A project's `.env` `DATABASE_URL` reached a local PGLite brain (#1931). Offline or off-VPN looked like a broken config, and dot-leading project names (`.claude`) blocked ingest forever (#2884). Transcripts from every repository shared one source (#2232). `/sync-gbrain` said "pushed" when the artifacts source had 0 indexed pages (#2670), forced the full dream cycle (#2783), always reported the call graph as unknown, and kept deleted worktrees' sources silently. The 1.27 artifacts-repo rename never worked, and installs it repointed at a repo that never existed are repaired (#1437). gbrain install and source wiring needed `jq` (#2369). +- **Codex.** A retired model (HTTP 404) or a custom provider with a wrong `base_url` reported `ready` (#2843). Custom providers configured with `env_key` failed the auth check (#2192). Large prompts failed on argv limits (Windows 32 KB, Linux E2BIG) and the macOS fallback timeout dropped the prompt (#1674, #1686). +- **Non-Claude hosts.** Later skill blocks lost `$GSTACK_*`, `$B` and `$D` and ran `/bin/gstack-...` (#1159). Codex skipped the symlinked router. design/dist and make-pdf/dist were missing from runtime roots (#2891). /ship and /plan-ceo-review overflowed the 160 KB host limit. The OpenCode `/gstack-review` command loaded nothing (#2651). `/browse` disappeared after toggling `skill_prefix` (#2263). Section files linked to checkout copies instead of rendered ones (#2707). skillify pointed at Claude's install on every host. /gstack-upgrade could run git cleanup in your own project when its path variable was unset. +- **Design.** A design binary killed at launch reported `DESIGN_READY`. /design-consultation and /design-shotgun never loaded your taste profile. Plan reviews read a root DESIGN.md as the feature's design doc (#2840). +- **/ship, /review and daily tools.** /ship stopped in versionless repos and monorepos (#2334, #2343). The plan audit picked an unrelated plan (#2797). Worktrees split project state (#2792). Learnings search exited 0 without Bun (#2794) and ranked poorly (#2796, #2799). Logs ignored `$TMPDIR` in sandboxed shells (#2952). /plan-tune said "calibrated" with no signals. /setup-deploy printed part of the Render API key (#1096). /analytics durations read 0s (#1728). Rejected taste preferences used the wrong confidence bucket (#1777). /context-save duration was empty on Linux (#2704). /retro missed pytest, minitest, XCTest, Terraform and Bats tests and counted build output (#2812, #2809, #2037). Plan reviews now treat design docs and handoff notes as data (#2818). +- **Browser.** Browse failed behind `HTTP_PROXY` (#494). Headless browse and make-pdf ignored `GSTACK_CHROMIUM_PATH` (#2771, #1968, #1633); extensions opened a window (#2281). `prettyscreenshot --hide ... out.png` wrote elsewhere (#1419). Stopping the browse daemon took over 5 seconds because every Chromium close waited out its timeout; it now takes about 0.1 s, and a closed headed display no longer waits a second on its own exit. Two browse daemons going headed at the same moment could pick the same free X display, and the loser failed the handoff; it now takes the next free display, and an Xvfb that fails to start reports its own error. `gstack-ios-qa-daemon --help` started the daemon (#1932). Aside in `~/.local/bin` was not found (#2965). +- **Windows.** /freeze, /guard and /investigate denied every edit and their hooks failed under cmd.exe (#2876, #2354). Generators crashed with EEXIST (#2329). The node-server bundle inlined sharp and socks (#2260), and a minimal install needed `browse/src/server.ts` (#2439). The /cso helper failed on the current SDK and was reported as "install Visual Studio" (#3015). `gstack-config gbrain-refresh` misread status (#1981). +- **Redaction.** Diagram and vector files flagged as phone and card numbers (#2885, #2827); function calls assigned to `session`/`token` (#2899); `.local.` config filenames (#2962); four-part versions (#2784); `postgres:postgres@localhost` (#2913). +- **Plan reviews.** /plan-eng-review writes its QA test plan even while some test choices are still pending, and its findings enter only through Scope Challenge C's saves. /qa-only's test value card no longer points at /qa's record step. /plan-ceo-review says what its closing cleanup does (old CEO plans are archived only with your approval). +- **Setup.** Auto-update wrote "just upgraded" after a failed setup and never retried. Conductor got the AskUserQuestion preference hook (#2208, #2207, #2719); office-hours asks its questions as `Q1...` prose there (#2729). Team mode's matcher missed Copilot's `skill` tool (#2229). A failed arm64 re-sign left a broken binary. + +#### Added +- `docs/troubleshooting.md`: every gate message, what it means, what is kept, and the command that fixes it. +- `gstack-memory-ingest --reconcile [--dry-run] [--limit N]` and `--request-reconcile`; `gstack-gbrain-sync --prune-gone-worktrees [--dry-run]`; `gstack-slug --adopt-legacy --from ` / `--dismiss `. +- `browse type --selector`, `browse profiles [list|prune --days N]`, `design --version`, `gstack-config set browse_extension_id `. +- For scripts: `bun lib/outside-review-result.ts --verdict ` prints `VERDICT: clean|findings|unverified|unavailable` (exit 0/3/4/1); the two-argument form is unchanged. + +#### For contributors +- New invariants with free tests: gate outcomes in `lib/gate-outcomes.ts` with a troubleshooting anchor per reason code; a fresh-shell conformance test and a quote-aware bash lint over every generated block on every env-var host; a per-host 160,000-byte SKILL.md ceiling. +- Eval children no longer receive credential env vars that carry a suffix (`GITHUB_TOKEN_1`, `GITHUB_APP_PRIVATE_KEY_BASE64`); admit one explicitly with `extraAllow` (#2949). Chunked large-push coverage for the pre-push guard (#2915). +- The test typecheck ratchet ignores the order tsc prints an object type's members in. +- The weekly eval report counts only cases that ran: tests deselected for another tier or case shard are listed by reason with no credit, every result is attributed to its case (0 unattributed, was 72), and no PR-lane eval shard runs over 10 minutes (the deploy eval is split per case). +- Codex evals run on their own CI job where Codex's sandbox can start, and pass only when Codex completed a command; a review whose sandbox failed now fails instead of passing. The `/codex review` eval passes only when Codex actually reviewed the diff. The AUQ matrix checks the `(recommended)` marker on option labels, where the auto-decide hook reads it. The `/plan-eng-review` QA test-plan eval now runs the real interactive review instead of a shortcut prompt that skipped the workflow: a periodic checkpoint resumes it at Test review from a recorded Scope Challenge, and the full fresh run is a non-blocking marathon case. + +Contributed by @CermakM (#2843), @Haijie-Lee (#2192), @SomSamantray (#2965), @Vaughan-g-aus (#2329), @BharadwajMittapelli (#2729), @maxpetrusenkoagent (#1931, #1932, #1981), @Infiniteyieldai (#2884), @afshaker (#2778), @time-attack (#2232, #2229), @jzeisweiss (#2670), @liutiming (#2783), @agile-operators (#1437), @benjaminberes-bp (#2369), @lewispeel (#2885), @Yugz29 (#2899), @fcmerle (#2962), @cdecook23 (#2784), @mb1810 (#2913), @ntdatt812 (#2949), @pneumorea-ai (#2915), @gabrielrondon (#2811), @kevingrasso32 (#2876), @xwang4-svg (#2354), @DizzyWesterwald (#2936), @oli548 (#2771), @shofel (#1968), @ryanlaiwy (#1633), @yinanli1917-cloud (#2281), @Spooks444 (#2260), @punksterlabs (#2439), @himiaocc (#2492), @jbetala7 (#1419, #1777), @mamedov (#2891), @Lockyer228 (#2651), @topcoder1 (#2263), @jonahberg (#2707), @AlinValentin7 (#3015), @philhie (#2208), @Alchemist-DevAI (#2334), @merit-blake (#2343), @day-of-davon (#2952), @loulanyue (#2797, #2792, #2794, #2796), @Bit0ps (#2840), @szsunyuan (#2799), @frosimanuel (#2818), @JiayuuWang (#1096), @RyanAlberts (#1728), @exGeni (#2704), @Jino00 (#2812), @snig-17 (#2809) and @SholtoMc (#2037). + ## [1.91.18.0] - 2026-10-03 **Transcript consent can cover only new sessions or only chosen repos, and Claude Code users can opt into a model-tuned skill overlay.** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 09ff72c699..4ba76804b5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -410,7 +410,7 @@ operator `~/.claude` config, MCP servers (gbrain, Conductor), skills, `~/.gstack decision logs, and `CONDUCTOR_*` env never leak into the child. The `GITHUB_` and `EVALS_` prefix rules preserve CI metadata but reject credential-shaped names such as `GITHUB_TOKEN`, `GITHUB_PERSONAL_ACCESS_TOKEN`, and -`GITHUB_APP_PRIVATE_KEY`. Named provider auth, runner `extraAllow` entries, and +`GITHUB_APP_PRIVATE_KEY`. The screen reads every underscore-separated segment, so a trailing qualifier does not carry a name past it (`GITHUB_APP_PRIVATE_KEY_BASE64`, `GITHUB_TOKEN_1`), while a segment that merely contains a credential word stays metadata (`GITHUB_PATH`, `GITHUB_TOKENIZER`). Named provider auth, runner `extraAllow` entries, and per-test overrides are deliberate exceptions; a name-based rule cannot identify a secret assigned to an arbitrary metadata name. This keeps local eval signal aligned with CI instead of disagreeing for reasons unrelated to the code under diff --git a/README.md b/README.md index 0d0cb33774..0ad7765c5a 100644 --- a/README.md +++ b/README.md @@ -625,6 +625,7 @@ Other references: [docs/gbrain-sync.md](docs/gbrain-sync.md) (sync-specific guid | [Browser](BROWSER.md) | How gstack drives Aside first (the contract, the cookbook, rendering, research), when the fallback engine kicks in, and the fallback's full `$B` command reference | | [Contributing](CONTRIBUTING.md) | Dev setup, testing, contributor mode, and dev mode | | [Memorable recall bridge](docs/memorable-workflow-memory.md) | Opt-in third-party workflow memory through gstack: two consents, what gstack hands over and can attest, removal, troubleshooting | +| [Troubleshooting](docs/troubleshooting.md) | Every `not run` / `unavailable` message, what it means, and the fix | | [Changelog](CHANGELOG.md) | What's new in every version | ## Privacy & Telemetry @@ -645,6 +646,10 @@ Data is stored in [Supabase](https://supabase.com) (open source Firebase alterna ## Troubleshooting +**A message says `not run`, `unavailable` or names a fix?** Look it up in +[docs/troubleshooting.md](docs/troubleshooting.md): every gate message gstack +prints, what it means, what was kept, and the command that fixes it. + **Skill not showing up?** Run `./setup --status` from your gstack checkout. It prints every install (host, scope, version, skills directory, source checkout) and, for a `stale`, `missing` or `unregistered` row, the exact command that fixes @@ -653,13 +658,19 @@ it. The usual fix is to re-run setup from that row's source for that host, e.g. A project install lives in the project's `.claude/skills/gstack` or `.agents/skills/gstack`; run its `setup` from inside the project. -**`/browse` (or `/qa`, `/design-review`) says `NEEDS_ASIDE` or `ASIDE_NOT_RUNNING`?** That's the probe telling you it's about to use the fallback browser. Want Aside? Open the app and sign in — `aside --version` should print a version and `aside repl 'console.log("ok")'` should print `ok` — then re-run. gstack never installs it for you. Want the fallback on purpose while Aside is open? `GSTACK_SKIP_ASIDE=1` makes every skill, the renderer, and `./setup` treat Aside as absent. +**`/browse` (or `/qa`, `/design-review`) says `NEEDS_ASIDE` or `ASIDE_NOT_RUNNING`?** That's the probe telling you it's about to use the fallback browser. Want Aside? Open the app and sign in — `aside --version` should print a version and `aside repl 'console.log("ok")'` should print `ok` — then re-run. gstack never installs it for you. Want the fallback on purpose while Aside is open? `GSTACK_SKIP_ASIDE=1` makes every skill, the renderer, and `./setup` treat Aside as absent. When Aside is absent the probe prints `NEEDS_ASIDE: ` and skills trust that line for the macOS-only download pitch; `GSTACK_PLATFORM` overrides the OS it names, for tests and unusual hosts (set it in your shell — gstack never reads it from a project `.env`). **`/browse` fails on the fallback browser?** `cd ~/.claude/skills/gstack && bun install && bun run build` **`/make-pdf` or `/diagram` can't render?** Same two paths: with Aside open they print through Aside (`bun run ~/.claude/skills/gstack/bin/gstack-render.ts some.html --screenshot /tmp/out.png` tests it directly, and its first line, `ENGINE=aside` or `ENGINE=browse`, names the browser that actually rendered); without it they use the bundled browser, so `bun run build` is the fix. -**Stale install?** Run `/gstack-upgrade` — or set `auto_upgrade: true` in `~/.gstack/config.yaml` +**Stale install?** Run `/gstack-upgrade` — or set `auto_upgrade: true` in `~/.gstack/config.yaml`. +On Codex and the other non-Claude hosts, upgrade from a terminal: find the +checkout in the `source` column of `./setup --status`, then +`cd && git pull && ./setup --host `, and start a new session. + +**Typing into a specific field with the fallback browser?** `browse type --selector '' ` +types into that element; bare `browse type ` types into whatever has focus. **State in the wrong place, or a setting that won't stick?** `~/.claude/skills/gstack/bin/gstack-paths --explain` shows which directory gstack uses for its state and why. See [docs/state-root.md](docs/state-root.md). diff --git a/TODOS.md b/TODOS.md index d5974552da..bc4f170d81 100644 --- a/TODOS.md +++ b/TODOS.md @@ -2,12 +2,127 @@ ## NEXT PRIORITY +### P2/P3: severe fix wave follow-ups (filed 2026-10-03, v1.91.19.0) + +Deferred from the approved severe fix wave (docs/designs/SEVERE_FIX_WAVE_2026_10.md), each with its reason. + +- **Enforce the Bun floor at setup (#2815)** — `engines.bun` is now `>=1.4.0` + (the CI pin), but Bun does not enforce `engines` and setup never checks the + version. Bun older than 1.3.3 silently ignores `--no-compile-autoload-dotenv` + and `--no-env-file` (verified with 1.3.2), so D0's protection against a + project's `.env`/`bunfig.toml` is silently off there. Setup should refuse or + warn below the floor. **Effort:** S. **Priority:** P2. +- **Windows Node CLI lane for Smart App Control (E3; #2596, #2595, #2124)** — + a `dist/browse` wrapper would shadow `browse.exe` for every Windows user; + launcher precedence (browse vs browse.exe vs browse.cmd under Git Bash and + cmd) and the node-missing message need a Windows SAC machine to verify. + **Effort:** M. **Priority:** P2. +- **C8 router wording behind a routing panel** — disabled skills now leave the + router, but the "When in doubt, invoke the skill" wording change (Tier 3) was + not run through a routing eval panel. **Effort:** S. **Priority:** P3. +- **Cross-block variables on fresh-shell hosts: make-pdf `$P`** — make-pdf + "exports" `$P` for later blocks, which Codex and the other env-var hosts lose + between blocks (same class as `$B`/`$D`, which the shared prelude now + re-derives). Add `P` to the runtime prelude or resolve it per block. + **Effort:** S. **Priority:** P2. +- **#2709's recorded-Chromium reap never runs on Playwright 1.62** — Playwright + 1.62's `Browser` has no `process()`, so `chromiumProcInfo` is always null and + the cleanup is a no-op (verified by probe). Record the PID another way (CDP + `SystemInfo.getProcessInfo` or the launch server). **Effort:** S. **Priority:** P2. +- **#2670's bash half in `bin/gstack-brain-sync`** — the TypeScript stage + verdict now reports a 0-page artifacts source; the bash brain-sync still has + no `index_failed` status file or page-count probe. **Effort:** S. **Priority:** P3. +- **Navigation guard acts after the request is sent** — Playwright routing + cannot see redirect hops, so a blocked hop resets the tab and fails the + command after the request left. Pre-send blocking needs CDP Fetch + interception, including out-of-process iframes. **Effort:** M. **Priority:** P3. +- **Redaction repo allowlist (#2598)** — a per-repo `+++ b/` skip list + (generated `*.svg`/`*.excalidraw`, #2827) for noise context rules cannot + cover. New config surface, so out of the wave. **Effort:** M. **Priority:** P2. +- **Global-discover Codex session schema (#2750)**, **ios-qa boot-token fixes + (#1837, #1975; need a device)**, **absolute bun path for compiled browse + spawns (#931; macOS repro unclear)** and **GitLab CI `curl | bash` (#1713)** — + out of the wave's scope. **Priority:** P3 each. +- **Upstream gbrain batch lookup** — a per-slug existence lookup and a separate + `pending` list in `gbrain import --json`, so the landing check need not list a + whole source and spawn `get` per page. **Effort:** M (upstream). **Priority:** P2. +- **Memory follow-ups** — record repo identity for gbrain sources at + registration (prune can prove ownership of a pre-wave source only from the + same repository's main checkout); a deliberate migration of pre-wave + transcripts out of `default` into per-repo sources (needs gbrain-side page + moves); decide #2778's "nearest existing ancestor" attribution for deleted + worktrees (risk: attributing to an unrelated parent repo like a dotfiles + repo). **Priority:** P3 each. +- **G2 on the TS twin** — `lib/bin-context.ts` (the Windows fallback) shares the + common-dir slug keying but does not record former worktree buckets. + **Effort:** S. **Priority:** P3. +- **Codex follow-ups** — post-run `codex_sandbox_unavailable` telemetry (only + the preflight logs it today); the Q2 first-use notice covers Codex only, not + the Codex host's Claude Code outside voice; the bash-native timeout watchdog + cannot reap grandchildren when `pkill` is missing; document enabling + unprivileged user namespaces in common containers. **Priority:** P3 each. +- **Live Codex multi-block check as a periodic paid case** — the wave ran it + once by hand (fresh `CODEX_HOME`, fresh clone, `./setup --host codex`, one + multi-block skill). Make it a periodic case so a regression shows up weekly. + **Effort:** M. **Priority:** P2. +- **/review's working-tree fingerprint under Codex's default sandbox** — the + live Codex check (v1.91.19.0) showed `bin/gstack-wtree` exits 1 inside + `codex exec -s workspace-write`, because Codex mounts `.git` read-only and the + fingerprint writes untracked content into `.git/objects`. `gstack-review-log + --start review` then fails with `cannot capture a diff review without a + working-tree fingerprint` (loud, not a false pass), and `git fetch` cannot + write `FETCH_HEAD`. Use a private object directory + (`GIT_OBJECT_DIRECTORY` + `GIT_ALTERNATE_OBJECT_DIRECTORIES` under + `$TMPDIR`) for the fingerprint, and report the fetch as stale-base coverage. + **Effort:** S. **Priority:** P2. +- **Closed by the wave's evals2 lane (run 37162480720):** Codex paid evals now + run commands in CI (host-run job; all 8 Codex cases executed real commands), + ship-exploratory-late-input no longer ends `inconclusive` (capture names the + probes to rerun after an input change), plan-eng-multi-finding-batching's + checker and office-hours-brain-writeback's prompt are fixed (3/3). + plan-eng-review-artifact (0/4 since #3017) measured a shortcut prompt that + skipped the workflow; it now drives the real interactive /plan-eng-review in + a PTY. A fresh run needs more than its 600 s budget (498 s pass; 595 s + timeouts in census 37174266054 and a local run), so the periodic case + resumes at Test review from that census run's real Scope Challenge ledger + (pass, 364 s) and the fresh run is the marathon case + plan-eng-review-artifact-full. +- **Gate-outcome telemetry** — count `not_run`/`unavailable` outcomes per + reason code from `lib/gate-outcomes.ts` so the most common gaps get fixed + first. **Effort:** S. **Priority:** P3. +- **/gstack-upgrade redesign around the install registry**, a **marketplace + distribution channel**, and a **placeholder convention for generated bash** + (`` vs real syntax in fences). **Priority:** P3 each. +- **Design CLI health subcommand** — the DESIGN SETUP probe uses `--version`; a + richer `design health` could check the API key and model access too. + **Effort:** S. **Priority:** P3. +- **Autoplan publication hook under cmd.exe** — `scripts/resolvers/composition.ts` + still registers `bash -c '