Wraps the whole verb dispatch in run() with .with_context(|| format!("repo {repo}"))
instead of threading context through ~34 individual verb files. A body-decode
failure surfaces from forgejo-api as a bare ReqwestError with no status code or
URL retained (no client-injection point to capture more), so without this the
error alone can't distinguish a mistyped org/repo from a transient flake — see
the recent hive-forge triage-automation thread this was filed from.
Split run()'s match into a dispatch() fn so the repo can be captured once before
dispatching and the with_context wrap applied once after, uniformly, regardless
of which verb failed.
fixeshyperhive/hyperhive#2839. pr_assign_reviewer's own doc-comment
claimed full idempotency (already-requested = no-op), but conflated
'still pending' with 'already reviewed' - Forgejo clears a fulfilled
reviewer from requested_reviewers, so re-requesting them isn't a
no-op, it dismisses the standing review. now checks latest_reviews
for a non-superseded review from the target user first and skips
the request instead of blindly posting it.
On a repo with no CI configured forgejo returns the combined-status
`statuses` field as an explicit `null` rather than `[]`.
`#[serde(default)]` only covers a *missing* key — a present null still
fails to deserialize, so `pr-status` died with
`invalid type: null, expected a sequence` instead of reporting the PR.
Deserialize the field through an `Option<Vec<_>>` so both null and
absent map to an empty vec.
Closes#2752.
hive-c0re, hive-claude, hive-forge and hivectl each ship a README.md but
never declared it in their package manifest, so cargo/docs.rs metadata
did not pick it up. Every other workspace member already sets the field;
this closes the gap left after the README backfill.
Forgejo reports `"state": ""` in the combined-status response for a
commit that has no CI contexts at all. The typed `forgejo-api` client
models that field as an enum with no empty variant, so deserialization
failed and both verbs died outright — on exactly the pull requests
where "no CI ran here" is the useful answer. `pr-merge` was the worse
of the two: the crash sat in its pre-merge readiness check, blocking a
merge it should have waved through.
Route both call sites through the existing raw-JSON escape hatch
(`Client::get_api_json`), which exists for this failure mode: the
crate pins one schema while the server tracks the latest release line.
A lenient local `CombinedStatus` keeps `state` a plain `String` and
the per-context statuses as opaque values, so an empty or unknown
state is reported rather than fatal. `status_state_str` and its enum
mapping go away with it.
Closes#2735
`fn main() -> Result<()>` let anyhow's Debug impl render failures with a
bare `Error:` header. hive-forge is almost always invoked from an agent's
bash task, where the completion wake points at the task's .out file - so a
failure that only writes to .err is easy to miss entirely (mara's "had no
clue it failed" on #2624).
Wrap the dispatch in a run() and own the failure path in main():
- prefix with the binary name (`hive-forge: FAILED: ...`) so the line is
unmistakably ours in a mixed transcript,
- render with {:#} (alternate Display), which keeps the full context chain
inline - plain Display would have dropped every `.context()` below the
top one,
- return ExitCode::FAILURE explicitly rather than relying on the Termination
impl.
Half of #2624: the other half (surfacing .err in the bash-mcp completion
when a task exits non-zero) is damocles's, per the issue thread.
Drop the API path from --org and the "Forgejo applies it to the initial
commit" mechanics from --default-branch (kept the user-facing caveat:
only takes effect with --auto-init). Swept the remaining verbs
(attachment-get, pr-reviews, attach, repo-add-collaborator, comment,
clone, pr-cmd router, …) — already user-relevant, no changes needed.
Continue trimming clap arg help to user-relevant info: drop the
token-bounded-paging rationale (list --page), the why-it's-required
note (lint no-reviewer), the `Forgejo Do:`/`force_merge` API internals
(pr-merge), and tighten diff --full. pr-status was already clean.
Drop implementation detail from the clap arg help (the `<verb> --help`
surface) — which API/endpoint, page-count math, persisted-vs-streamer
log-source internals, refspec shapes — keeping only what/when-to-use for
each flag. Module `//!` docs (dev-facing, not shown by `--help`) left
intact.
Drop implementation mechanics from the `--help` surface, keep only
what a user needs to run the command:
- global `-r`/`-f`/`--json`: remove token-file paths, the bash-helper
history, and the "already-JSON verbs ignore --json" aside.
- verb `about` strings (repo-create/repo-labels/repo-search/artifact-get/
ci-log/ci-rerun/pr-commits): drop which-API / "no REST endpoint" /
web-route / workflow-dispatch internals and cross-refs.
Per-verb arg help (verbs/*.rs) trimmed in follow-up commits.
Now that matrix-sdk 0.18 is on main, reqwest 0.13.1 is already in the
tree transitively. Point the workspace crates at it directly.
reqwest 0.13 renamed the rustls feature set:
- rustls-tls -> rustls
- rustls-tls-native-roots -> rustls-native-certs
- (webpki-roots is now a separate feature)
hive-forge keeps its dual-trust story (system/native store for the
hive CA + bundled Mozilla roots for public CAs) by enabling
rustls-native-certs + webpki-roots explicitly.
forgejo-api 0.11 resolves cleanly against reqwest 0.13 (no conflict).
rusqlite 0.40 is intentionally NOT bumped here: matrix-sdk-sqlite 0.18
still pins rusqlite 0.37, so 0.40's libsqlite3-sys 0.38 would hit the
links="sqlite3" single-owner conflict. Deferred until upstream moves.
argus flagged (approving) that an unvalidated label could build a path
outside the state dir; mara called it out as a usability issue in its
own right, not just a low-risk security nit — a typo'd label should
give a precise 'not a valid label' error, not a confusing file-not-found
or an unexpected traversal.
Reject anything outside the plain lowercase+digits+hyphens charset
dashboard/extra_forges.rs already enforces on write, before touching
the filesystem at all.
Targets a dashboard-provisioned external forge account (FORGES tab)
instead of the internal forge: resolves `forge-<label>-token` for the
token and `forge-<label>.json`'s base_url for the URL, the same two
files dashboard/extra_forges.rs writes, instead of
HIVE_FORGE_URL/forge-token. Falls back to today's behavior when unset.
Orthogonal to -r/--repo.
An unknown label gives a clear error listing the labels actually found
in the state dir instead of a raw file-not-found. The base_url JSON key
is read via a typed sidecar struct pinned to what extra_forges.rs
writes, so the read side can't silently drift from the write side.
The live run-view streamer returns a single snapshot of whatever act_runner
had buffered by poll time. For a job that's still running that's fine (no
persisted log exists yet), but for a job that already finished it silently
truncates wherever the snapshot happened to stop -- the original bug: a
failed nix flake check run returned only the ~90s eval-phase prefix and
dropped the actual build-phase error entirely.
Flip the priority: try the durable persisted-log download first (complete
once it exists), fall back to the streamer only when nothing's persisted
yet (run still live). --step still goes straight to the streamer since the
persisted log is flat and doesn't honor per-step framing.
- Remove the two issue-number tags from Rust comments (tracker-tag lint).
- Correct the module doc: the durable download is keyed by the per-repo
run NUMBER + attempt segment (matches the code + persisted_logs doc),
not a global id — the earlier 'global id' wording was stale.
- Replace the fragile e.to_string().contains("out of range") branch with
a typed StreamerMiss enum (StepOutOfRange vs Unavailable), so a usage
error can never silently fall through to the persisted-log path if a
message string changes.
ci-log drove only the live run-view streamer (POST .../runs/<n>/jobs/<j>
with logCursors), which reads the live act_runner task record. Forgejo
prunes that record once a run completes, so the streamer 500s with
'task ... resource does not exist' on quick or older runs even though
the web UI still shows the logs — the reader (argus, atlas) then had to
ask the operator to relay the error.
Add a fallback: when the streamer errors or returns no lines, download
the persisted whole-job log via the same web route the run page's view-
raw-logs link uses, .../runs/<n>/jobs/<job>/attempt/<a>/logs, keyed by
the per-repo run number with the attempt segment. It survives the task
prune. Live and recent runs keep the rich per-step streamed view; only
the pruned case takes the flat fallback (where --step can't apply). New
--attempt flag (default 1) selects the run attempt for re-runs.
Verified against a real pruned run whose streamer 500'd: the fallback
prints the full persisted log; a completed short job ends cleanly at
'Job succeeded', confirming the route returns complete logs.
Address argus review on #1938's reopen verb: public fns returning Result
need a # Errors doc per the workspace convention. Document the patch_json
and print_json failure modes.
hive-forge had close but no reopen, so reopening required the non-obvious
workaround 'issue edit <n> --state open'. Add a reopen verb mirroring close
(PATCH state=open), wired into both 'pr reopen' (kind-checked PR) and
'issue reopen' (kind-checked issue). Updates docs/forge.md + the subcommand
enumerations.
The clap subcommand doc in main.rs still described the old web-route
implementation (--job, 'POSTs the rerun web action'), which surfaces in
hive-forge --help. Update it to match the workflow-dispatch rework.
The previous implementation POSTed Forgejo's run-page rerun web route,
which is CSRF-gated and answers a bare token POST with 404 — so the verb
never actually re-ran anything against the agent token.
Rework it to dispatch a fresh run of the workflow via the
GitHub-compatible workflow-dispatch API
(POST /repos/<o>/<r>/actions/workflows/<workflow>/dispatches {"ref":<branch>}),
which accepts a plain agent token (verified end-to-end on Forgejo 15.0.3).
A dispatched run is equivalent to the old empty-commit retrigger, minus
the commit.
The branch is resolved from exactly one of --pr (the PR head branch),
--run (branch + workflow looked up from that run in the Actions runs
list), or --branch (directly); --workflow picks the workflow file for
--pr/--branch (default ci.yml). Dispatch re-runs the whole workflow, so
the old --job single-job variant is dropped.
Also add workflow_dispatch to .forgejo/workflows/ci.yml for explicitness
(Forgejo 15.0.3 dispatches the pull_request workflow without it, but the
trigger makes the API path intent-clear and cross-version robust), remove
the now-unused Client::post_web_no_content, and update docs/tools/forge.md.