Skip to content

fix(2119): stop reporting empty --model formal-ai runs as successes - #2120

Merged
konard merged 19 commits into
mainfrom
issue-2119-ae2d4c9d7f6d
Aug 1, 2026
Merged

fix(2119): stop reporting empty --model formal-ai runs as successes#2120
konard merged 19 commits into
mainfrom
issue-2119-ae2d4c9d7f6d

Conversation

@konard

@konard konard commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #2119.

What was wrong

Three hive-mind solve --model formal-ai runs on 2026-07-30, one per --tool, produced zero lines of source code between them — and Hive Mind reported all three as successes. The AI side of that is Formal AI's problem and is reported upstream. This pull request is about our side: every signal Hive Mind published about those runs was wrong.

Run Tool Language What was published
019fb330-00e1-… agent Scala Provider: OpenCode Zen, Token usage: 0 input, 0 output, 11 AI sessions under a limit of 5, exit 0
019fb330-fa49-… claude Kotlin Calculated by Anthropic: $0.252315 for a free model, then ## ✅ Ready to merge on an empty diff
019fb331-c107-… codex Rust A pull request containing only our own .gitkeep, described as 1 file(s) modified

For contrast, the same generator in 2025 produced two-file, 45–81 line pull requests with a conventional model. The pipeline works when the model answers; what it cannot do is notice when the model does not.

What this changes

Thirteen defects, each with a regression test. Every fix is applied across all six tools (claude, agent, opencode, codex, qwen, gemini), not only where the symptom was observed. The Docker images also pin Formal AI 0.317.0, which contains the upstream generalized recovery fix.

Attribution and costsrc/formal-ai-pricing.lib.mjs is now the single source of the provider name and the zero-cost override, consulted at the cost boundary of all six tools plus agent-commander and the cost renderer.

  • D1 Provider: OpenCode Zen / Calculated by AnthropicLink.Assistant, because the tool that ran says nothing about who served the tokens
  • D2 $0.252315 for a free model → $0.00
  • D3 Public pricing estimate: unknown$0.00 (Free model)

Telemetrysrc/json-stream.lib.mjs frames records by balanced JSON values instead of by newlines, so pretty-printed, concatenated and chunk-split records all parse.

  • D4 Token usage: 0 input, 0 output for a session that used 44 511 tokens → real counts, for every tool
  • D12 the credential sanitizer redacting "tokens": [REDACTED] and JSON braces → counters and structure survive, secrets still do not

The restart loopsrc/auto-restart-budget.lib.mjs replaces two independent loops that shared one flag.

  • D5 Auto-restart 1/55/5 and then Auto-restart triggered (iteration 1)(iteration 5) — 11 AI sessions under --auto-restart-max-iterations 5 → one budget, one N/M label
  • D6 exhausting the budget exited 0 with the work undone → fails visibly, with auto-commit on fail recovery, exit 1
  • D11 ?? .formal-ai/ counted as user changes, re-created by every restart so the blocker never cleared → src/ai-tool-scratch.lib.mjs excludes AI scratch state via .git/info/exclude

Not calling nothing a success

  • D8 - 1 file(s) modified for an empty net diff → measured from the diff, not from per-commit sums
  • D9 ## ✅ Ready to merge on a pull request that changes nothing → an empty diff is a blocker, not a green light
  • D13 the solver's own .gitkeep / CLAUDE.md placeholder counted as the AI's work → matched by generated content (a repository's own .gitkeep still counts) and reported as placeholderOnly
  • D10 a working-session summary made of pwd output, leaking /tmp/gh-issue-solver-… → summaries carry facts or say they have none
  • D7 PR titles published as 'Implement Hello World in Scala', quotes included → command-stream escapes interpolations itself; the redundant quotes are gone everywhere they appeared

Tests

Nine new suites, all in the default suite (npm test, currently 368 files):

test-formal-ai-pricing-2119 · test-agent-stream-json-2119 · test-token-telemetry-sanitization-2119 · test-auto-restart-budget-2119 · test-empty-pull-request-2119 · test-shell-quoting-2119 · test-working-session-summary-2119 · test-ai-tool-scratch-2119 · test-formal-ai-uniform-tools-2119

The last one is the anti-regression net for the whole class: it asserts that all five stream parsers import the shared framer, all six tools reach the formal-ai pricing path, and all four Docker image definitions pin Formal AI 0.317.0, so a tool or image variant cannot silently retain a divergent implementation.

Final verification after merging current main: all 368 default test files passed under Node 24, together with ESLint, Prettier, MJS syntax checks, file-line limits, duplication analysis, changeset validation, and git diff --check.

Case study

docs/case-studies/issue-2119/ holds the reconstruction the issue asked for: minute-by-minute timelines of the three runs, an inventory of all six konard/test-hello-world-* repositories (2025 baseline vs 2026), the requirement-by-requirement table, root cause and fix for each of the thirteen defects, the two deliberate non-changes, where the data was insufficient and what instrumentation was added, and the libraries considered for the JSON framing (jsonhilo, concatjson, clarinet, json-stream-es, stream-json) with the reason we did not use them. Raw evidence — logs, issue/PR JSON, diffs, gist URLs — is archived under data/.

Upstream resolved and shipped

  • link-assistant/formal-ai#879 was fixed by Formal AI PR #881 and released in 0.316.1. It adds a seed-driven, bounded recovery ladder with cross-run learning, requires observable non-scratch workspace effects, fails closed on public endpoint diversion, and emits completion telemetry across all six clients. Hive Mind now pins the current 0.317.0 release in every Docker image.
  • link-assistant/agent#285 was fixed by Agent PR #286 and published in npm package 0.25.4. Unknown models now get the full prompt with an explicit logged default, product identity is centralized, and session versions come from the package manifest. Hive Mind's image install resolves to the current 0.25.5 package.

Adding .gitkeep for PR creation (default mode).
This file will be removed when the task is complete.

Issue: #2119
@konard konard self-assigned this Jul 30, 2026
konard added 12 commits July 30, 2026 14:51
Downloads the PR/issue metadata, conversation comments, review comments
and full solution-draft logs of the three konard/test-hello-world-*
reproductions cited in issue #2119 so the case study analysis is based
on immutable local evidence.
`--model formal-ai` runs reported "0 input, 0 output" tokens even though the
tools produced real usage. Formal AI emits pretty-printed JSON objects rather
than NDJSON, and codex writes partial lines, so the per-line parsers silently
dropped every event that carried the usage metadata.

Adds src/json-stream.lib.mjs, a shared incremental framer that accepts both
NDJSON and multi-line/pretty-printed JSON and buffers partial lines, and routes
the agent and codex stream readers through it.

Reproduction: konard/test-hello-world-019fb330-00e1-73b9-955e-f357a1600d5b#2
The credential sanitizer treated token telemetry (`"total_tokens":44511`,
`tokens=44511`, `"maxTokens": 8192`) and JSON structural braces as secrets, so
published logs and comments in the reproduction PRs showed redacted counters and
mangled JSON, hiding the real usage numbers.

Reproduction: konard/test-hello-world-019fb331-c107-78c7-8ff6-9f127a3c593c#2
The reproduction PRs attributed `--model formal-ai` to "OpenCode Zen" or
"Anthropic" and billed it: the claude path published $0.252315 taken from
Anthropic's `total_cost_usd`, and the public pricing estimate read "unknown".
Formal AI is served by Link.Assistant and is free.

Adds src/formal-ai-pricing.lib.mjs as the single source of the provider name and
the zero-cost override, and applies it at the cost boundary of all six tools
(claude, agent, opencode, codex, gemini, qwen) plus the agent commander and the
cost-info renderer, which now prints "$0.00 (Free model)" instead of "unknown".

Reproductions:
- konard/test-hello-world-019fb330-fa49-7c9d-a664-b7ea33bb698a#2
- konard/test-hello-world-019fb330-00e1-73b9-955e-f357a1600d5b#2
Hive Mind had two independent auto-restart subsystems, each with its own counter
reading the same --auto-restart-max-iterations flag. solve.mjs runs both in one
process, so a limit of 5 permitted 10 AI sessions, and the reproduction PR shows
the two incompatible labels the issue calls out: "Auto-restart triggered
(iteration 1)" next to "Auto-restart 1/5 Log". Neither path failed at the limit,
so a run that never resolved its blocker still exited 0 with the uncommitted work
discarded along with the temporary clone.

- src/auto-restart-budget.lib.mjs: one process-wide iteration counter both loops
  claim from, and one N/M label formatter (N alone when the limit is 0/unlimited).
- src/auto-restart-exhaustion.lib.mjs: the single exhaustion path - log, auto-
  commit and push the uncommitted work via the existing critical-error recovery
  helper, post one "limit reached" comment, and record the failure.
- solve.finalize.lib.mjs exits 1 when the budget was exhausted, so the result is
  actually visible instead of being reported as a success.

Reproduction: konard/test-hello-world-019fb330-fa49-7c9d-a664-b7ea33bb698a#2
All three reproduction pull requests were titled `'Implement Hello World in
Scala'` with the single quotes as literal characters.

Root cause: command-stream shell-escapes every interpolated value, quoting it
whenever it contains a character the shell would split on. Wrapping the
placeholder in quotes - `--title "${updatedTitle}"` - therefore passes
`--title "'Implement Hello World in Scala'"` and the quotes land in the title.
Values without spaces were unaffected, which is why it went unnoticed.

Fixed at every such site in src/ and scripts/ (PR titles and body files, the
YouTrack issue sync, the claude runtime switch, reviewers-hive, the changeset
and release scripts). Where the quotes belonged to an inner language the
expression is now assembled in JS and passed as one argument: the fork-lookup
jq filters and the issue/PR-link GraphQL queries.

tests/test-shell-quoting-2119.mjs pins the command-stream behaviour that caused
this and scans src/ and scripts/ so the pattern cannot come back.
…to merge

Two of the three reproduction runs produced a pull request with an empty net
diff (the AI tool wrote nothing, so the branch held only the solver's own
scaffolding commit and a revert of it), yet both published

    ### Changes
    - 1 file(s) modified
    - 1 line(s) added

and the Kotlin run additionally posted "## ✅ Ready to merge ... - No pending
changes". Merging that would have closed the issue with nothing implemented.

src/pull-request-changes.lib.mjs is now the single place that answers "does
this pull request change anything", used by both the description writer and the
mergeability watcher. A diff that could not be read is reported as unmeasured
rather than as empty, so a transient API failure cannot trigger restarts.
Formal AI writes a .formal-ai/ plan directory into the workspace it runs in, so
every uncommitted-changes check saw

    ?? .formal-ai/
    📝 Found uncommitted changes
    🔄 AUTO-RESTART: Restarting Agent to handle uncommitted changes...

and restarting recreated the directory, so the blocker could never clear. The
same state reached 'git add -A' on the auto-commit paths, which would have
published a tool's private scratch files in the user's pull request.

This generalizes the .playwright-mcp/ special case from #1124: the paths are
written to .git/info/exclude, so git itself stops reporting them and all eight
copies of checkForUncommittedChanges agree without each needing its own filter.
The exclude file is local to the clone, so nothing leaks into the diff.
The Kotlin reproduction run posted

    ## Working session summary

    The `pwd` command completed. Output:
    /tmp/gh-issue-solver-1785421161275

on a pull request whose diff was empty. The AI tool returning nothing useful is
an upstream problem; what Hive Mind owns is that the comment read as a report of
completed work and published the solver's private workspace path.

The summary now carries an explicit notice when the pull request still contains
no changes, and solver workspace paths are replaced with <workspace>. An
unreadable diff produces no notice, so a transient API failure cannot turn into
a false "nothing was implemented" claim.
`formal-ai with <tool> --verbose` emits pretty-printed, multi-line JSON
records. Commit 2119700 fixed the resulting "Token usage: 0 input,
0 output" for agent, opencode and codex by framing records on balanced
JSON values instead of on newlines; gemini and qwen kept their own
line-based parsers and so still had the identical defect.

Both now share `takeJsonRecords` from json-stream.lib.mjs, which also
handles records concatenated without a separator (issue #1250) and
records split across process chunks.

tests/test-formal-ai-uniform-tools-2119.mjs pins the uniformity down:
same stream shapes, same token accounting and the same Link.Assistant
$0.00 pricing across all six formal-ai tools.
The Rust reproduction PR contains exactly one file - hive-mind's own
auto-generated .gitkeep placeholder - so the change stats reported
"1 file changed" for a run that implemented nothing.

pull-request-changes.lib.mjs now splits the diff per file and drops the
sections whose content matches what solve.auto-pr.lib.mjs writes into
.gitkeep / CLAUDE.md, exposes placeholderOnly, and offers
buildEmptyPullRequestBlocker() so the restart reason names the
placeholder. Matching is on the generated content, so a repository's own
.gitkeep stays a real change.
Compiles every artifact of the three 2026-07-30 reproduction runs into
docs/case-studies/issue-2119: the issue and its comments, all six
test-hello-world repositories (three 2025 baselines for contrast), the
three session logs, the PR/issue JSON, the net diffs and the URLs of all
13 published session logs.

README.md reconstructs the timeline of each run, lists every requirement
from the issue with where it is addressed, gives the root cause and fix
of all 13 Hive Mind defects, records the two deliberate non-changes and
why, reviews the existing libraries considered for each fix, and states
where the data was insufficient and what instrumentation was added.

upstream-formal-ai.md is the consolidated Formal AI report: five defects
with reproductions, workarounds and code-level suggestions, framed as a
request for generalization and self-healing rather than per-symptom
patches.
konard added 3 commits July 30, 2026 17:08
Files the consolidated Formal AI report as formal-ai#879 and, from the same
logs, three Agent CLI defects as agent#285: unknown model ids fall through to
the without-todo prompt, that prompt tells the model it is opencode and routes
feedback to sst/opencode, and session records carry a hard-coded
agent-cli-1.0.0 version.

Section 9 is corrected too - the system prompt *is* in the archived log, in the
request bodyPreview records, which is how the prompt-selection defect was
found.
…batim

The release workflow requires exactly one changeset per pull request and
forbids manual package.json version edits, so the bump is declared here.

The archived case-study data is raw evidence, so it is added to .prettierignore
alongside every earlier case study's data directory rather than reformatted.
.gitkeep was committed to open this pull request; the solution replaced it, so
it is not part of the change. This is the same placeholder D13 teaches the
change counter to ignore.
@konard konard changed the title [WIP] --model formal-ai not working on simplest hello world fix(2119): stop reporting empty --model formal-ai runs as successes Jul 30, 2026
@konard konard closed this Jul 30, 2026
@konard konard reopened this Jul 30, 2026
@konard
konard marked this pull request as ready for review July 30, 2026 17:56
@konard

konard commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Working session summary

PR 2120 is finished and green.

Self-review of the full diff — completed the remaining portion (qwen.lib.mjs, reviewers-hive.mjs, the five solve.* modules, working-session-summary.lib.mjs, youtrack-sync.mjs, plus tests/ and scripts/). Two things I checked rather than assumed:

  • solve.watch.lib.mjs now returns success / reason / autoRestartLimitReached. solve.mjs:1326 is the only caller and reads only latestSessionId, latestAnthropicCost, autoRestartIterationsRan, lastIterationLogUploaded, so the added fields are informational — the actual non-zero exit flows through finalizeSolveProcess (src/solve.mjs:1498), the single terminal path, which reads the shared exhaustion state.
  • formatAutoRestartLabel() called with no argument in the auto-merge log title defaults to the current shared counter (src/auto-restart-budget.lib.mjs:99), so it renders the same N/M as the explicit call sites.

The only pre-existing tests I modified are in tests/test-agent-token-usage.mjs, where three cases asserted the old buggy behaviour (stepCount, 0 for concatenated JSON); they now assert the recovered counts. No feature was removed.

CI. Every code-bearing job had been skipped on this branch, because the last two pushes were docs/chore-only and detect-code-changes.mjs only inspects the latest push for synchronize events. ready_for_review is not a workflow trigger and workflow_dispatch falls back to HEAD^..HEAD (also chore-only), so I closed and reopened the PR — a reopened event compares the full base..head range. Run 30567138911 is success with every job green, including the ones that had never run on this code: test-compilation, test-suites, test-execution, check-file-line-limits, memory-check-linux, lint, validate-docs, Check for Changesets, Check for Manual Version Changes.

Final state. Working tree clean, 16 commits ahead, origin/main is an ancestor of HEAD, no comments on issue #2119 or PR #2120 to respond to, and the PR is now out of draft: #2120

Upstream reports filed as the issue asked: formal-ai#879 and agent#285.

One caveat worth stating plainly: the local tests/test-hive.mjs run in this sandbox intermittently fails a --dry-run case against its 15 s execSync timeout. I benchmarked it interleaved against an origin/main worktree (main 16132 ms vs branch 15526 ms average over 5 runs each) — the branch is not slower, and the same suite passes on CI, so it is sandbox timing, not a regression. I did not raise the timeout.


This summary was automatically extracted from the AI working session output.

@konard

konard commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Solution Draft Log

This log file contains the complete execution trace of the AI solution draft process.

💰 Cost: $52.696379

📊 Context and tokens usage:

Claude Opus 5: (13 sub-sessions)

  1. 114.6K / 1M (11%) input tokens, 10.9K / 128K (9%) output tokens
  2. 114.5K / 1M (11%) input tokens, 10.0K / 128K (8%) output tokens
  3. 116.3K / 1M (12%) input tokens, 36.2K / 128K (28%) output tokens
  4. 116.3K / 1M (12%) input tokens, 23.7K / 128K (18%) output tokens
  5. 114.8K / 1M (11%) input tokens, 33.0K / 128K (26%) output tokens
  6. 116.8K / 1M (12%) input tokens, 28.8K / 128K (22%) output tokens
  7. 116.8K / 1M (12%) input tokens, 31.1K / 128K (24%) output tokens
  8. 116.9K / 1M (12%) input tokens, 33.4K / 128K (26%) output tokens
  9. 116.4K / 1M (12%) input tokens, 29.8K / 128K (23%) output tokens
  10. 116.3K / 1M (12%) input tokens, 31.8K / 128K (25%) output tokens
  11. 116.3K / 1M (12%) input tokens, 29.6K / 128K (23%) output tokens
  12. 115.4K / 1M (12%) input tokens, 10.5K / 128K (8%) output tokens
  13. 81.6K / 1M (8%) input tokens, 6.8K / 128K (5%) output tokens

Total: (50.5K new + 1.2M cache writes + 56.7M cache reads) input tokens, 479.4K output tokens, $52.674294 cost

Claude Haiku 4.5:

  • 9.4K / 200K (5%) input tokens, 532 / 64K (1%) output tokens

Total: 9.4K input tokens, 532 output tokens, $0.022085 cost

🤖 Models used:

  • Tool: Anthropic Claude Code
  • Requested: opus (claude-opus-5)
  • Thinking level: high (~23999 tokens)
  • Main model: Claude Opus 5 (claude-opus-5)
  • Additional models:
    • Claude Haiku 4.5 (claude-haiku-4-5-20251001)

📎 Log file uploaded as Gist (24200KB)


Now working session is ended, feel free to review and add any feedback on the solution draft.

@konard

konard commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

✅ Ready to merge

This pull request is now ready to be merged:

  • All CI checks have passed
  • No merge conflicts
  • No pending changes

Monitored by hive-mind with --auto-restart-until-mergeable flag

@konard

konard commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Both upstream issues are closed and new versions releases, double check that this issue is fully implemented and prepare pull request to be merged.

@konard
konard marked this pull request as draft August 1, 2026 06:58
@konard

konard commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

🤖 AI Work Session Started

Starting automated work session at 2026-08-01T06:58:39.683Z

The PR has been converted to draft mode while work is in progress.

This comment marks the beginning of an AI work session. Please wait for the session to finish, and provide your feedback.

@konard konard closed this Aug 1, 2026
@konard konard reopened this Aug 1, 2026
@konard

konard commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Double-check complete:

The PR description has been updated with the released upstream fixes and final verification.

@konard
konard marked this pull request as ready for review August 1, 2026 08:04
@konard

konard commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Solution Draft Log

This log file contains the complete execution trace of the AI solution draft process.

💰 Cost estimation:

  • Model: GPT-5.6 Sol
  • Provider: OpenAI
  • Public pricing estimate: $27.460299

📊 Context and tokens usage:

  • 409.6K / 200K (205%) input tokens, 35.1K / 128K (27%) output tokens

Total: (409.6K + 21.8M cached) input tokens, 35.1K output tokens, $27.460299 cost

🤖 Models used:

  • Tool: OpenAI Codex
  • Requested: gpt-5.6-sol
  • Thinking level: medium (~15999 tokens)
  • Model: GPT 5.6 Sol (gpt-5.6-sol)

📎 Log file uploaded as Gist (5398KB)


Now working session is ended, feel free to review and add any feedback on the solution draft.

@konard
konard merged commit 44f0631 into main Aug 1, 2026
68 of 75 checks passed
@konard

konard commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

🎉 Auto-merged

This pull request has been automatically merged by hive-mind.

  • All CI checks have passed

Auto-merged by hive-mind with --auto-merge flag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--model formal-ai not working on simplest hello world

1 participant