chore(release): v0.76.0 - #838
Merged
Merged
Conversation
The Glama build spec lived only in a web form on glama.ai, so nothing in this
repository described it and nothing could check it. Two builds failed with
could not start the proxy Error: spawn gflow ENOENT
The image built fine. `uv sync` installs the project into /app/.venv and puts
console scripts in /app/.venv/bin, which is never added to PATH, so a CMD
naming a bare `gflow` misses and the container exits before answering a ping.
A failing container is listed but excluded from Glama search results, which
also blocks the awesome-mcp-servers listing that gates on a Glama score.
glama.json is now the tracked copy of that spec, and tests/test_glama_build_spec.py
guards it the way test_server_json.py and test_plugin_manifests.py guard the
other distribution artifacts: the spawned executable must be a console script
this project defines, it must be an absolute path into the uv venv rather than
a bare name, a build step must create that venv, and the subcommand must match
every other artifact.
Written test-first, and A/B-controlled against the spec exactly as it failed:
with `gflow` the venv-path test fails with the real cause, with the corrected
path all five pass.
Verified end to end on Glama build 01a0a3c1-c0a3-7462-bb4b-350a715ca63b —
success in 31.2s, answering initialize plus tools/list with 15 tools, 2 prompts
and 3 resources, no browser and no credentials.
No docs change here on purpose: docs/DISTRIBUTION.md is rewritten on the pending
v0.75.0 release branch, so its Glama row is updated there rather than conflicting.
Review finding on #819: _server_argv split on `--` without ever checking the prefix, so a spec that dropped or swapped mcp-proxy would pass every test. The proxy is load-bearing. Glama pings the container over HTTP while gflow speaks stdio; mcp-proxy is the bridge, and the successful build logged `starting server on port 8080` from it. Without it the container would start gflow and still fail the check because nothing is listening — the same shape as the ENOENT bug: a container that runs but never answers. A/B-controlled by swapping the proxy for another name, leaving the rest of the spec intact: exactly one test fails, with its own message, and the other five still pass.
… gate see them Adds the two dimensions of distribution that nothing described. docs/MARKETPLACES.md — where gflow-cli comes from, and what each channel actually delivers. The channels differ more than they look: PyPI gives the CLI, the Claude Code plugin gives skills plus the MCP server but not the CLI, the Codex plugin gives skills only, the MCP Registry gives metadata, and the Glama container gives a server that introspects but cannot generate. PyPI is named as the single authoritative source. docs/CONTAINER.md — the container dimension. What glama.json is, what the automatically-built image provably does (initialize + tools/list, 15 tools, no browser, no credentials) and provably cannot do (anything that reaches Flow), plus the recorded reasons there is no Docker Hub image and no Docker MCP Catalog entry: their CONTRIBUTING requires shareable test credentials, and a container cannot hold a logged-in Chrome session. Written as a deliberate "not yet" with the condition that would change it. Charters are kept disjoint on purpose: DISTRIBUTION.md owns submission status, MARKETPLACES.md owns what a channel delivers, MCP.md keeps the plugin consent explanation. Status rots weekly; what a channel delivers does not. Neither page is mirrored to the website. The mirror only rewrites links for root-sourced files, so publishing one while its siblings stay unpublished would ship broken site links. check_doc_links.py now globs docs/*.md. This was found by control, not by reading: a deliberately broken link in the new page passed a green gate. FILES listed 22 of 122 top-level docs, so 100 -- every LIVE_VERIFICATION_* record among them -- were unaudited while the gate reported success. Coverage goes from 64 files to 186. That surfaced 9 genuinely broken links, now fixed by unlinking targets nobody can follow (absolute file:// paths that only resolved on the author's machine; specs/plans since consolidated into memory) while keeping the evidence text verbatim. Nothing is grandfathered, and the glob means a new page is covered without anyone remembering a list. Verified by A/B: the same control link passes the old gate and fails the new one with exit 1.
Several cross-platform instructions were shown only in PowerShell, so a reader on Linux or macOS met a block they could not run and reasonably concluded the project expects Windows. The CI matrix says otherwise: ProfileLease runs on ubuntu, macos and windows, and the test matrix runs three Python versions. Fixed, POSIX-first with the PowerShell difference noted inline: - AGENTS.md — the Impeccable Routine, the contributor gate list, was a ```powershell block whose only Windows-specific line was the env var. All nine gates are unchanged and none was trimmed. - docs/DEBUGGING.md — the incident-bundle grader was `.venv/Scripts/python.exe` inside a ```bash fence, which is wrong on both counts; now `uv run python`. The --verbose filter and the image-gen repro gain POSIX forms alongside the PowerShell ones. - docs/MCP.md — the Claude Desktop and Cursor examples hardcoded the maintainer's own dev path; now /path/to/gflow-cli. - AGENTS.md, docs/MARKETPLACES.md — `codex plugin ...` is shell-agnostic and was fenced as powershell. - docs/AGENT_UI_E2E.md — the FORCE_AGENT_UI repro. Deliberately left alone, because they are genuinely Windows and already say so: DEBUGGING's "Windows console encoding" section (cp1252, and it already tells Bash/WSL users to skip it), the gflow.exe launcher file-lock in USAGE and PROJECT_STATUS, the chrome.exe/python.exe process scans in KNOWN_ISSUES, and the .ps1 scripts (record_demo, register_task, agentic_image_e2e) which really are PowerShell. USAGE.md and CONFIGURATION.md needed nothing — they already lead with bash and label the PowerShell variant.
The comment said the canonical routing index is `docs/index.md`. It is `docs/INDEX.md` — there is no lowercase file, and git records only the uppercase one. No behaviour change and no latent bug: "index.md" is in WEBSITE_ONLY, and _source_for() returns on that membership check before it ever touches the filesystem, so the casing never reached a lookup that a case-sensitive CI runner would resolve differently from a Windows dev box. The comment was simply wrong about a filename, which is the kind of thing someone later trusts instead of checking. Noted while auditing index coverage: every top-level doc except the 57 LIVE_VERIFICATION_* release records is referenced from docs/INDEX.md, and no top-level doc carries YAML frontmatter (only superpowers/memory notes and skills/*/SKILL.md do, where a loader consumes it).
feat(distribution): track the container build spec, document every channel, and close the link-gate blind spot
A fork PR whose CI is awaiting approval renders as fully green.
GitHub holds `pull_request` workflows from forks at conclusion
`action_required` until a maintainer clicks "Approve and run" — and such a run
does **not appear in the PR's statusCheckRollup at all**. Branch protection
evaluates that rollup, and so does every human glancing at the checks list, so
the PR presents as passing with zero test evidence behind it.
Measured on 2026-09-15 across three open fork PRs, identical labels, all three
cross-repository — so "it's a fork" is not the discriminator, the approval click
is:
#781 CI conclusion=success -> 16 checks shown, all green
#793 CI conclusion=action_required -> 2 checks shown, both green
#787 no pull_request run at all -> 2 checks shown, both green
#793 had already shipped broken tests and a ruff-format failure through exactly
this gap with nothing red anywhere.
check_fork_pr_ci.py reports only the states the rollup hides. An outright
failure is already visible and an in-progress run shows as pending, so neither
is flagged — a gate that cries wolf is a gate people learn to ignore. A passing
`External PR Triage` never counts as coverage either: it runs on
pull_request_target, which executes for forks regardless of approval, and
treating it as evidence would restate the bug.
Wired into pr-council-review step 0.6, whose existing fallback was blind here:
it inspects `gh pr checks`, which returns all-green for exactly these PRs.
Verified against the three real PRs it was built from: #781 exits 0, #793 and
#787 exit 1 with distinct reasons.
Two rows were honestly marked "only observable after upload". v0.75.0 is now on PyPI, so they are closed with evidence rather than left as permanent question marks — which is what the ledger asked for. Row 6, PyPI metadata -> verified: version 0.75.0, the rewritten summary, 18 classifiers and the Documentation/Repository/Changelog/Homepage/Funding sidebar links, read back from the PyPI JSON API. Row 7, MCP Registry -> unblocked, not done. The ownership token in the *published* README reads `io.github.ffroliva/gflow-cli` and matches server.json exactly, which was the whole blocker. What remains is tooling, not this release: mcp-publisher is not installed and its `login github` is an interactive device-code flow. Also recorded: `mcp-publisher init` would OVERWRITE the existing server.json — use `validate`. DISTRIBUTION.md's Glama row said `submitted`, which stopped being true hours later. It is listed, claimed and rated A, with a passing container build. The section now also records what the channel actually is: Glama generates the Dockerfile from a web form and clones the repo at the default branch's head, so it never touches PyPI; two builds failed on `spawn gflow ENOENT` because `uv sync` puts console scripts in /app/.venv/bin, which is never on PATH; and the admin panel's percentage is profile completeness, not the public letter grade that awesome-mcp-servers asks for.
fix(ci): catch fork PRs whose required CI never ran, and close the v0.75.0 ledger
…ect the claim I wrote "the container cannot generate, because generation needs a signed-in Chrome session" into docs/CONTAINER.md, issue #822 and a marketing draft. It came from architecture, not from a run. The run contradicts the strong form. Its evidence base was the Glama health-check image, which deliberately installs no browser. "It cannot generate" was true of THAT image and was generalised into a fact about containers — the exact failure skills/spike/SKILL.md exists to prevent: evidence about the image, read as evidence about the feature. Measured today, $0, no generation attempted: gflow --version in container -> gflow, version 0.75.0 /opt/google/chrome/chrome -> present, channel stable is_playwright_chrome_channel_available -> True is_chrome_available -> True real Chrome headed under Xvfb -> 153.0.8010.36, real page title navigator.webdriver -> None headless UA marker -> False gflow's OWN detector passes in the container, not merely Playwright's. And with gflow's actual launch arguments the browser presents no automation markers — G12 keys on navigator.webdriver, which is absent here. Xvfb is a real X server, so Chrome runs genuinely headed; that is not Chrome's --headless mode, which is what Google rejects. Three of the spike's own failures are recorded in the note because each looked like a finding and was not: xvfb without xauth; a probe that bypassed the entrypoint so Chrome had no DISPLAY; and navigator.webdriver=True read from a raw Playwright launch gflow never performs. The third would have confirmed the prior belief. Ships the protocol as runnable artifacts — docker/Dockerfile (real Chrome, not Chromium, because browser_manager accepts exactly /opt/google/chrome/chrome) and docker/docker-compose.yml with a named auth volume, so the container stays disposable and the identity does not. `serve` refuses to start without GFLOW_CLI_DAEMON_TOKEN by design; docker compose config fails loudly, which is the intended posture and documented as such. Still UNMEASURED and labelled as such, not inferred: the one-time interactive Google sign-in inside a container, whether Flow accepts the session afterwards, and whether a Windows-made profile survives the move to Linux. A clean fingerprint is a necessary condition, not a sufficient one.
Follow-up to the spike. Two corrections to my own reasoning, both prompted by review: 3.11 was chosen because it is the project's requires-python FLOOR. That is a reason for the minimum supported version, not for a runtime image. 3.13 was then chosen because CI's matrix is ["3.11","3.12","3.13"] and 3.14 would be untested. That argument is weaker than it looks for a container: the image pins its interpreter, so it is deterministic rather than an ambient version that might surprise a user. The honest fix is to verify the image, not to ship an older interpreter. So: 3.14, with the contract measured rather than assumed. python : 3.14.7 gflow_cli : 0.75.0 chrome channel : True chrome available : True initialize : OK, server "gflow-cli" tools/list : OK, 15 tools Recorded in docker/README.md as the check to re-run after any base bump.
… pass in it Runs gflow's own offline suite inside the container to settle the interpreter question with a measurement instead of a preference. CI's matrix is ["3.11","3.12","3.13"], so this is the first time the suite has run on 3.14: 4379 passed, 28 skipped, 138 deselected in 368.59s (6m08s) Performance baseline, same machine: runtime image (Chrome + Xvfb) 1.65 GB test image (repo + dev deps) 714 MB cold start -> gflow --version 1475 / 1516 / 1511 ms cold start -> initialize + tools/list 2228 / 2406 / 2495 ms uv sync during build 14.8 s The MCP figure is the one that matters for agents: ~2.3 s from cold container to a usable tool list, paid once by a long-lived serve and per-call by docker run. Two tests fail in that run and CANNOT pass in a container built from a git WORKTREE. test_real_tree_passes_root_doc_check and test_the_generator_ships_only_what_git_tracks both shell out to `git ls-files` to introspect the repository. In a worktree `.git` is a 71-byte pointer file: gitdir: C:/development/github/gflow-cli/.git/worktrees/container-spike Docker copies the pointer faithfully and it dangles inside the container: fatal: not a git repository: /app/C:/development/.../worktrees/container-spike giving CalledProcessError exit 128 for both. This is the build context, not the interpreter — they test repo state, not runtime behaviour. Recording a wrong diagnosis too, because it cost a rebuild: I first attributed this to `.git` being in my .dockerignore, fixed that, and the failures were byte-identical. The dockerignore was never the mechanism. Naming a plausible cause without measuring it is the same error this whole spike exists to correct. Also moves the spike scaffolding out of the repo root into docker/ — an untracked Dockerfile.test and .dockerignore at the root are exactly what the hygiene gate polices, and dockerignore.example is the copy-to-root form.
docs(container): a container CAN host gflow — measured, and the claim corrected
…en question Glama 0.75.0 is released from a build that logged cli_version 0.75.0, and Auto-Release is on. Records what took a session to find: there is no write API, the synced head can lag GitHub by hours, the pin field rejects unsynced commits (so it stays empty), and Auto-Release has never fired, so whether it builds the tag or a stale branch head is unverified until the next release. Also marks punkpeye #14423's Glama gate as met and Composio #1905 as ready-to-merge, and points CONTAINER.md at the released build. Claude-Session: https://claude.ai/code/session_012VFJzGCdPcB22F6oqQonA4
docs(distribution): record the Glama release, Auto-Release and its open question
`mcp-publisher init` only writes a *template* server.json. This repo already has a real one, committed and version-locked by tests/test_server_json.py, so init refuses it with "Error: server.json already exists" and exits 1. That aborted the documented `init && login && publish` one-liner at its very first command, before login was ever reached. Replaced init with validate in both places, and recorded why. validate checks the committed file against the live registry and leaves it untouched. Measured with mcp-publisher 1.8.1 on 2026-09-15: init -> "Error: server.json already exists", exit 1, file byte-identical validate -> "server.json is valid", exit 0 The earlier note claiming init OVERWRITES server.json was wrong; it refuses. The defect is the non-zero exit breaking the chain, not data loss.
…ence docs(distribution): fix the MCP Registry publish sequence
tests/mcp/test_cli_parity.py enforces parity at the command and option level.
Neither can see the queue payload, where a key written under one name and read
under another type-checks, lints, passes every test, and does nothing at runtime.
The new gate extracts the payload keys mcp/tools.py writes (dict literals and
later subscript stores) and the keys anything under worker/ reads (.get(), [],
and "k" in payload), then fails on any written key the worker never reads. The
reader set is the whole worker package on purpose: codec.py builds the request
objects, but daemon.py consumes project_id, project_name, tool_specs and
output_file directly, so diffing tools.py against codec.py alone would report
four false positives.
Only that direction is an error. A key the worker reads but the MCP tools never
write is legitimate — the CLI serve path and worker/queue.py enqueue tasks too.
It failed on its first run, on a real defect:
mcp/tools.py:875,954 write payload["project_name"]
worker/daemon.py:193 read payload["project_title"]
Nothing in the repository has ever written project_title — `git log -S` over all
history returns nothing — so create_project(title=...) always received the
hardcoded fallback "gflow-cli images". An agent calling gflow_generate_image with
project_name="Client pitch deck" got no error and a project named "gflow-cli
images". The parameter is documented on both generate tools as the title for a
freshly created project, and has been dead since it shipped on 2026-07-26: the
daemon's read landed four days earlier, and the --project-name feature wired up a
new key instead of the one already being read. The CLI was unaffected; it resolves
the title itself in cli_image.py.
The fix is the one-line read. The behavioural test asserts the created project
carries the agent's title, and was watched failing first:
Expected: mock(title='Client pitch deck')
Actual: mock(title='gflow-cli images')
The extractors carry their own tests against synthetic sources, including a
written-but-unread key and a misspelled reader, so the gate is proven able to go
red rather than merely observed green.
Verified: 279 passed across tests/worker and tests/mcp; ruff, ruff format,
pyright src (0 errors), hygiene, doc links, website mirror + PII, council memory.
Closes #628
test(mcp): gate the payload-key round trip, and fix the drop it found
The registry listing is the last unshipped distribution channel, and the one that feeds the others: PulseMCP ingests it and GitHub's MCP gallery is built on it. It was documented as a manual three-command sequence that nobody had run. Ordering is why this hangs off `release: published` rather than the tag push. mcp-publisher proves namespace ownership by reading the `mcp-name:` token out of the PUBLISHED PyPI README, and PyPI metadata is frozen per release, so the wheel has to be up first. release.yml publishes to PyPI and only then creates the GitHub Release, so by the time this fires the token is live. Auth is GitHub Actions OIDC. No personal access token is created, stored, or handed to a third-party service — the runner exchanges its own short-lived identity token, which is why the job takes `id-token: write` and nothing else. The mcp-publisher download is pinned by version AND sha256: that job publishes publicly under io.github.ffroliva while holding an OIDC write scope, so a moved upstream tag must not be able to change the binary that does it. workflow_dispatch covers the re-run case — a release whose PyPI upload succeeded but whose registry publish did not. `validate` runs first on both paths, so a bad server.json stops before anything is sent. Not automated, deliberately: `init`. It only writes a template server.json, refuses when a real one exists, and exits 1 (see #827). Both triggers resolve the workflow from the default branch, so this goes live when it reaches main at the next release. v0.75.0 itself still needs either one manual publish or the arrival of v0.76.0; DISTRIBUTION.md says so rather than implying the channel is already shipped. Verified: yaml parses; `zizmor --offline --min-severity low` clean over the new file and over .github/workflows/ as a whole; doc links, hygiene, website mirror and PII gates green. mcp-publisher 1.8.1 `validate` already passes against the live registry on this server.json.
Two defects in the compose stack, both mine from the container spike earlier today.
1. It pointed at a service that does not exist. The `login` comment told non-Linux
users to "use the VNC service below instead". There is no VNC service — the file
defines `login`, `gflow` and `serve`, and nothing in docker/ mentions VNC anywhere
else. That is a false instruction shipped in the file people read first.
2. The X11 socket was hard-coded to /tmp/.X11-unix, which is the wrong path on the
one non-Linux host that can actually run this. Measured on Windows 11 + WSL2
(Ubuntu), 2026-09-15:
/mnt/wslg/.X11-unix -> srwxrwxrwx ... X0 (live socket, owned by the user)
/tmp/.X11-unix -> empty directory
DISPLAY -> :0
So WSLg does provide a real display, and the default mounted an empty directory
next to it. Chrome then exits immediately against a display that is not there,
which reads as "containers cannot do interactive login" rather than "wrong path".
The mount is now ${X11_SOCKET:-/tmp/.X11-unix}: unchanged for Linux, one variable for
WSL. Verified both resolutions with `docker compose config`:
default source: /tmp/.X11-unix
X11_SOCKET set source: /mnt/wslg/.X11-unix
docker/README.md gains a "Where the display comes from" section with the per-host
table and the two prerequisites that actually bite: run it from inside WSL (Git Bash
rewrites /mnt/wslg/... into C:/Program Files/Git/mnt/wslg/...), and switch on Docker's
WSL integration for that distro or the CLI inside WSL cannot reach the daemon at all.
Not fixed here, because it is not ours to fix: the Google sign-in itself still needs a
human at the keyboard. This change is about making sure that human gets a window.
Verified: compose config parses and both mount paths resolve; doc links, repo hygiene,
website mirror and PII gates green.
Follow-up on the same branch, from two questions worth answering with evidence. 1. The version pin had no gate. `RUN pip install --no-cache-dir gflow-cli==0.75.0` was hardcoded and nothing tied it to pyproject.toml. At v0.76.0 the image would keep installing 0.75.0 while docker/README.md's "Verified on this image" table went on quoting the new version — the same defect class as #628: one value in two places with nothing checking they agree. tests/test_server_json.py already does this for server.json; this is the container's equivalent. The version is now ARG GFLOW_VERSION, gated by tests/test_dockerfile_version_pin.py. The gate was A/B-controlled: set to 0.74.0 it fails naming both versions, restored it passes. It also asserts the install step actually interpolates the ARG, because a pin nothing reads is decoration. 2. Layer ordering, and a trap in it. The order was already right — Chrome's apt layer before pip — so a version bump reuses ~1.6 GB from cache and only the last layer rebuilds. But adding an ARG can silently destroy that, and the instinctive placement (beside FROM) is the wrong one. Measured on a throwaway image with a unique marker, so no stale cache entry could match, and with a positive control confirming the version layer did rebuild in both arms: ARG after the expensive layer -> expensive layer CACHED ARG before the expensive layer -> expensive layer REBUILT A first attempt at this A/B was contaminated: an earlier run had already cached the target value, every layer reported CACHED, and the experiment discriminated nothing. The positive control is what caught it. The measurement above is the clean rerun. The ordering is asserted by the same test file, so a tidy-up that moves the ARG up fails rather than quietly costing minutes per build. 3. Ownership of every input is now named. Base image -> Dependabot, via a new docker ecosystem entry for /docker. gflow-cli -> ARG + the gate above. Chrome -> a rebuild, deliberately unpinned: Google's apt repo keeps only the current build so a pinned version stops resolving within weeks, and Dependabot cannot see packages installed by a RUN regardless. The consequence is now written down: that layer caches indefinitely, and --no-cache is what refreshes it. Verified: 4 new tests pass and fail correctly under mutation; ruff, ruff format, doc links, repo hygiene, website mirror, PII and council-memory gates green; dependabot.yml parses with all four ecosystems.
fix(docker): the containerised sign-in could not work on Windows
feat(ci): publish to the Official MCP Registry on release, via OIDC
…it 39) An account with no Flow subscription is served Flow's own "you don't have access" screen instead of the app. gflow had no route for that state, so the editor sweep reported the only thing it could see -- the settings trigger never appeared -- and told the user Google had changed their frontend and to file a bug, attaching an incident bundle holding a screenshot of their own signed-in account page. raise_if_known_landing() is already the single chokepoint all three "we landed somewhere unexpected" raise sites route through, so the guard goes there: it probes the DOM for flow-pinhole-unavailable-screen before it reads the URL and raises FlowAccessUnavailableError (exit 39, not retryable), whose remediation names the subscription and links Google's eligibility page. Detection is by component because the spike refuted every cheaper signal: no entitlement field on the wire across 21 responses, HTTP 200 with a client-side hop so no 3xx to key on, and an unstable path (/unavailable and /u/8/unavailable both observed, the second carrying Google's account-index segment). The component is locale-invariant by construction; the screen's prose is not. Verified live at $0 on a real unentitled account as an A/B through the same command: probe neutered gives exit 23 "file a frontend bug", probe restored gives exit 39. The MCP twin is run rather than reasoned about -- the new worker test drives the real process_task and reads the persisted queue row back. Scope, measured rather than assumed: the same account without --project never reaches the editor. It takes the labs REST arm and dies at project.createProject with HTTP 401, still reported as AuthExpiredError. That row, and auth login / auth status, read an oracle that cannot discriminate an abandoned sign-in from a missing entitlement, and on the login path Chrome has already closed by the time the verdict runs. Left for its own change. Refs #756, #773
…d act on the council Eleven review dimensions ran against bf171b0. Three findings were defects in that commit, not polish. The labs arm was unguarded (D1). `_enter_editor` returns early when --project is supplied, with no readiness gate, so the guard in its gallery arm never ran and the failure surfaced from _switch_to_{image,video}_mode instead -- still exit 23, still "file a bug". Both raise sites now consult raise_if_known_landing first, and a new offline scenario covers them. A/B'd like the migrated arm: with the guard removed the scenario fails with UiSelectorDriftError("... may be a new Flow UI layout this gflow-cli version does not recognize yet"). It cannot be run live here -- every profile we hold is served flow.google.com -- so the neutered control is the proof. The CHANGELOG rewrite had deleted PR #828's shipped release note (D9). Git would have taken that on merge with no conflict. Restored. safe_page_url leaked Google's account-index segment into error text and, through redact_error_detail, into the failed-operation row (D3). It is never diagnostic -- gflow has no /u/N handling anywhere -- and an earlier draft of the test pinned the raw ordinal in place. Collapsed to /u/N/; the test now pins its absence. Also: the 47-line class docstring was a second copy of the CHANGELOG entry and carried the over-claim D1 falsified, so it is 18 lines and a link to the spike (D14); the probe lost 10 lines of scaffolding the try already covered and gained a log.debug so a dead locator engine is visible (D14, D3); a replicated EXIT_CODE_MAP walk that no daemon change could fail was deleted in favour of the test that drives the real process_task (D14, D4); the explode stand-in now fails at `await count()`, where Playwright does, and the retryable kwarg is asserted rather than inferred from class-default behaviour (D4); the spike awaits Request.response() and redacts before truncating (D15, D3). Docs: carve-out 5 recorded in the drift memory with the control-flow lesson; MCP.md stops asserting one cause for "Signed in to Google, but not to the Flow app" and gains an exit-39 envelope row; USER_GUIDE.md likewise; the spike names the command it measured. Refs #756, #773
…-unavailable # Conflicts: # CHANGELOG.md
mcpservers.org approved the submission on 2026-09-15 and the listing is now live at /servers/ffroliva/gflow-cli. Adds the badge their approval mail supplies, and corrects DISTRIBUTION.md, which still recorded the channel as "submitted". Verified rather than assumed, because the listing 403s to curl: badge.svg 200, image/svg+xml /servers/ffroliva/gflow-cli 200, 115643 B, mentions gflow-cli /servers/ffroliva/<bogus> 404 (control — the 200 is not a catch-all) The 403 is user-agent bot-blocking; with a browser UA the page serves. Their follow-up pitches a paid sponsorship for placement on the site and in awesome-mcp-servers. The free listing is what we took, and the badge does not depend on it — recorded so the next person does not read the mail as a prerequisite.
fix(transports): route Flow's unavailable screen to its own error (exit 39)
docs(distribution): mcpservers.org listing is live — badge + status
Three surfaces stop lying to you, and the last unshipped distribution channel starts publishing itself. - An account with no Flow access is told so, via its own exit 39, instead of being shown a selector-drift error and invited to file a bug (#833). - The containerised sign-in works on Windows and no longer points at a service that does not exist; the image pins its own version (#830). - An MCP agent's `project_name` is finally consumed — the worker had been reading a key nothing ever wrote (#628). - The Official MCP Registry publishes itself on release via OIDC (#829), and mcpservers.org approved the listing (#834). Version bumped in all six sites the hygiene gate checks: pyproject.toml, src/gflow_cli/__init__.py, .codex-plugin/plugin.json, plugins/gflow/.claude-plugin/plugin.json, server.json (x2) and uv.lock. Note for the next cut: skills/release/SKILL.md step 6 lists only the first three plus the plugin manifest — server.json and uv.lock are not in it, and only the hygiene gate caught them. That list wants fixing separately. Live verification: docs/LIVE_VERIFICATION_v0.76.0.md. $0, reads only. The decisive arm is #833's NEGATIVE control, run first-hand rather than relayed — `flow-pinhole-unavailable-screen` is 0 on both ffroliva and denon82 while `aisandbox-root` is 1, so the guard does not fire on accounts that have Flow access and the zero is a real absence rather than a page that never booted. Both accounts were in the /about state, which is where a presence/absence signal would have been mimicked, and this one was not. Recorded as NOT verified, with reasons: the Docker sign-in needs a WSL-integrated Docker host; the MCP Registry publish cannot be observed until this tag reaches main, since both triggers resolve the workflow from the default branch. #791 is NOT in this release. A fix passed eight council dimensions and was then withdrawn: it read the rendered DOM and claimed the signal was server-attested, which #756 refutes — the /about hop is decided client-side with zero requests to Flow, and the backend grants access while the frontend declines to open it. The probe would have reported authenticated users as logged out. PR #835 stays a draft. Gates at this tree: ruff, ruff format, repo hygiene, doc links, website PII, website mirror, council memory, release artifacts, uv lock --check — all pass. Wheel builds as 0.76.0: 138 entries, zero ZIP duplicates, console script present.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
CI caught what no checklist names: `docker/Dockerfile`'s `ARG GFLOW_VERSION`. The gate that caught it shipped in #830, in this same release. tests/test_dockerfile_version_pin.py AssertionError: docker/Dockerfile installs gflow-cli==0.75.0 but this repo is at 0.76.0 That makes seven version sites across four disagreeing lists (#839): the four in skills/release/SKILL.md step 6, plus server.json (x2) and uv.lock which only check_repo_hygiene.py knows, plus this one which only its own test knows. SonarCloud's red was downstream, not independent — "No report was found for sonar.python.coverage.reportPaths using pattern coverage.xml", because the test job died before writing one. What was NOT bumped, deliberately: - docker/README.md's "Verified on this image" block and its what-is-verified table quote MEASUREMENTS — `gflow_cli : 0.75.0`, `✅ measured — gflow, version 0.75.0`. The container was not re-measured for 0.76.0 (it needs a WSL-integrated Docker host, recorded as a named blocker in LIVE_VERIFICATION_v0.76.0.md), so editing those numbers to match would fabricate a run. A quoted measurement that tracks pyproject.toml is not a measurement. The staleness is now stated in the file instead, next to the numbers, with what would settle it. The test's own docstring describes exactly this failure — the table "quietly became a false claim" — so making it true by editing would have inverted the gate's intent while satisfying it. - "as of v0.75.0" in docker-compose.yml, "Fixed in v0.75.0" in KNOWN_ISSUES, and the measured build output in docs/CONTAINER.md are all historical. Also updated: DISTRIBUTION.md's Official MCP Registry row said "ready — submit once v0.75.0 is on PyPI". #829 automates it and ships here, so the first run is this release's own job. Gates re-run green: hygiene, doc links, website PII, website mirror, council memory, release artifacts, and the docker pin test (4 passed).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v0.76.0 — alpha
Three surfaces stop lying to you, and the last unshipped distribution channel starts publishing itself.
UiSelectorDriftError(exit 23) — gflow blaming its own selectors and inviting a bug reportARGproject_nameis finally consumed — the worker read a key nothing in the repo has ever writtenLive verification
docs/LIVE_VERIFICATION_v0.76.0.md— $0, reads only.The decisive arm is #833's negative control, run first-hand rather than relayed:
The guard does not fire on accounts that have Flow access, and
aisandbox_root = 1makes that zero a real absence rather than a page that never booted. Both accounts were in the/aboutstate — the state where a presence/absence signal would be mimicked — and this one was not, because it reads a component that exists only on the unavailable screen.Recorded as NOT verified, with reasons: the Docker sign-in needs a WSL-integrated Docker host; the MCP Registry publish cannot be observed until this tag reaches
main, since both triggers resolve the workflow from the default branch.#791 is not in this release
A fix passed eight council dimensions and was then withdrawn. It read the rendered DOM and claimed the signal was server-attested; #756 refutes that — the
/abouthop is decided client-side with zero requests to Flow, and "the backend grants access while the frontend declines to open it." The probe would have reported authenticated users as logged out, reintroducing #791 as its own cure. PR #835 stays a draft; the HTTP findings and plumbing are reusable.Nothing in the changelog over-promises without it — #833's entry explicitly records the auth/REST rows as still unfixed.
Gates at this tree
ruff·ruff format --check· repo hygiene · doc links · website PII · website mirror--check· council memory · release artifacts ·uv lock --check— all pass.Wheel builds as
0.76.0: 138 entries, zero ZIP duplicates, console script present.Note for the next cut
skills/release/SKILL.mdstep 6 lists four version sites. There are six — it omitsserver.json(x2) anduv.lock, and only the hygiene gate caught them. That list wants fixing separately rather than as a rider here.