Skip to content

Mechanical fixes from third audit pass - #63

Merged
orda-aska merged 1 commit into
mainfrom
fix/third-audit-mechanical
Oct 29, 2026
Merged

orda-aska merged 1 commit into
mainfrom
fix/third-audit-mechanical

Conversation

@ahrazzle

@ahrazzle ahrazzle commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Mechanical fixes from a third audit pass. Each one has a test that fails without it.

Changes

ion/cli.py (cmd_report): the digest was marked sent only when len(errs) < len(notifiers). But errs from notify() includes every failed outbox retry. Once one notifier had a backlog, mark_digest_sent never ran, so every later run re-sent the same digest to the working notifiers and queued another copy for the failing one. When it recovered, it received all the copies at once. README says "A digest is sent only when something changed." Every failed delivery is already queued and retried, so the digest is now marked sent after every attempt.

ion/execute/autofix.py: the executor gave the fixer {"default_branch", "description"}, while the preview (fleet.preview) gave it the repo's full snapshot. A plugin that reads any other field (languages, topics, license, private) previews one diff, gets it approved, and then always fails at execution with "the change differs from the approved diff … the repo changed since approval". Re-proposing never helps. The executor now passes the same snapshot. The built-in fixers only read description, so they are unaffected.

ion/fleet.py (DependabotConfig.find_repos): it targeted repos whose .github/ion.yml says ignore: true or enabled: false; readme-skeleton already skips them. It now skips them too.

ion/planconfig.py: autofix proposals and merge follow-up archives are never analysis candidates. plan-config listed them as "no longer a candidate under the new config" for any config, including an identical one. That effect is now reported only for proposals that were candidates under the current config.

ion/cli.py (main): standalone ion propose, sync-approvals and execute returned 0 even when they reported errors, including execute --apply hitting a breaker trip. They now exit 1, as ion run already did.

ion/analyze/lifecycle.py, ion/analyze/__init__.py: archive evidence added +0.2 and "no other repo references it" even when some live repos had no loaded mirror (failed first clone, run --no-mirror), so their references were never read. With alpha-core depending on old-tool and alpha-core's mirror missing, old-tool qualified for archiving. The claim and the +0.2 now need every live repo to have been read; otherwise the reason says "references unknown: N other repos were not analysed". This fails safe: while any mirror is missing, archive candidates that relied on the +0.2 do not qualify.

ion/execute/split.py (has_refs): the "refuse to push into a non-empty repo" check ran ls-remote with check=False. It read a failure (network, auth) as an empty remote. If a pre-existing target with matching visibility had content only on a non-base branch, the split then pushed its history alongside that content. An unreadable remote now raises SafetyError. As with a failed clone or push in any step, that ends the proposal as failed.

ion/propose/scoring.py: README says "the score moves by 0.15 or more". In floats 0.7 - 0.55 and 0.6 - 0.45 are just under 0.15. So a rise of exactly rescore_delta was still suppressed by cooldown, and that move did not re-plan. The difference is now rounded before the comparison.

Not in this PR

Filed separately because the fix involves a design choice:

Validation

uv run pytest -q: 259 passed (250 existing, 9 new). All 9 new tests fail with the ion/ changes reverted. scripts/check_licenses.py: 0 not allowed.

Automated posting by agentic team with human oversight.

🤖 Generated with Claude Code

https://claude.ai/code/session_016PpXZJdMN5FExq9THy5VNg

Mechanical fixes from a third audit pass, each with a test that fails
without it:

- cmd_report marked the digest sent only when fewer notifiers failed
  than were configured, but outbox retry failures count as errors. Once
  a notifier had a backlog, every run re-sent the same digest and
  queued another copy. Failed deliveries are already queued, so the
  digest is now marked sent after every attempt.
- The autofix executor gave the fixer {default_branch, description}
  while the preview gave it the full snapshot. A plugin reading any
  other field could never be applied ("the repo changed since
  approval"). It now gets the same snapshot.
- dependabot-config targeted repos whose .github/ion.yml says
  `ignore: true` or `enabled: false`; readme-skeleton already skipped
  them.
- plan-config listed autofix and follow-up proposals as "no longer a
  candidate" under any config, including an identical one.
- `ion propose`, `sync-approvals` and `execute` exited 0 when they
  reported errors, including a breaker trip. They now exit 1, as `run`
  does.
- Archive evidence said "no other repo references it" (+0.2) even when
  some live repos' mirrors were missing, so their references were never
  read.
- Split's "refuse to push into a non-empty repo" check read an
  `ls-remote` failure as an empty repo. It now refuses.
- A score rise of exactly rescore_delta (0.55 -> 0.7) was still
  suppressed by cooldown, and a 0.45 -> 0.6 move did not re-plan,
  because of float rounding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PpXZJdMN5FExq9THy5VNg

Copy link
Copy Markdown
Contributor Author

The ci workflow on this PR ends in startup_failure with no jobs started. That is not this PR: every run in the repo has failed the same way since 2026-10-18, including the pushes of #50 and #59 to main, and the workflow files have not changed since 2026-10-13. It looks like a repository or organisation Actions setting. Details are in #73. There is no code fix to port. Locally, the full suite passes (259). #61, #62 and this PR merged together also pass (275).

Merge note: open PR #48 already conflicts with main in ion/fleet.py. This PR adds a trivial append-at-end conflict with it in tests/test_misc.py.

Automated posting by agentic team with human oversight.


Generated by Claude Code

@ahrazzle

Copy link
Copy Markdown
Contributor Author

Independent review by a different agent than the drafter. Verdict: approve.

What I checked. Eight small fixes, each in its own file, each with the reason in a comment.

The ones with safety weight: the split executor's has_refs now fails closed when ls-remote errors, instead of treating an unlistable remote as an empty one. Archive candidates no longer gain the "no other repo references it" score when some live repos were never analysed. The autofix executor verifies against the same snapshot shape the preview used.

The rest are correctness fixes: digest marked sent once queued in the outbox, stage commands exit non-zero when a stage reports errors, Dependabot candidates honour repo overrides, plan-config no longer mislabels follow-up proposals, and the rescore comparisons round away float noise.

Suite on the branch: 259 passed, against 250 on main, in a clean run.

One behaviour change worth naming: a digest is now marked sent even when a notifier failed, on the grounds that the failure is queued in the outbox and retried. That reverses the previous at-least-one-success rule. The reasoning is sound and documented in the code, and the old behaviour risked duplicate sends. Flagging it here so the merge is a conscious choice.

This PR closes none of the open issues: its findings were new in the third audit pass, and it explicitly leaves #68, #69, and #70 open.

This is posted as a comment because the reviewer's account is the same GitHub account that authored the PR, so GitHub will not accept a formal approving review from it. Review is requested from the other maintainer accounts. Under the repo rule (a PR merges once a different agent than its drafter has reviewed it), this review is the cross-agent pass.

Automated posting by agentic team with human oversight.

@orda-aska
orda-aska merged commit c1d2c4a into main Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: bug Something does not work as it says it does

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants