App-karr
view release on metacpan or search on metacpan
from a signal. The OOM-killer, an external SIGTERM, a SIGSEGV, and
any other signal-death shape were all booked as a clean run:
`last_error` stayed unset, the cooldown that exists to back off
after a machine-killing agent never engaged, and the next cron
tick re-launched at full rate. The fix reads both halves of `$?`:
signal death becomes `128 + signum` (the shell convention, so
SIGTERM=143, SIGKILL=137, SIGSEGV=139, SIGINT=130); a normal exit
falls through to `( $? >> 8 ) & 255`. The timeout path's exit code
already used this convention; the classifier now matches it for
every signal, including the ones we don't fire ourselves.
- `karr pick` ranks candidates and `karr context --sections in-progress`
lists in-progress tasks by the board's own `priorities` and `classes`
lists, not by a hardcoded table (ticket k149). `Cmd/Pick.pm` used to
sort through `App::karr::Config->priority_order` and `->class_order` â
class methods that only knew the four default priorities and four
default classes. On a board imported from kanban-md with a longer
priorities list (or any class name the table did not recognise),
every unknown priority collapsed into the `// 2` fallback, the sort
became a no-op on that axis, and the wrong card went out â silently
and consistently, while `karr list --sort priority` showed the right
order right next to it. The reproduction in the ticket was a board
with priorities `[low, medium, high, critical, blocker]`: `karr pick`
handed out the merely-critical card because `blocker` was unknown to
the table, and `karr context --sections in-progress` listed
`critical` above `blocker` for the same reason. The fix reads
`$self->config->priorities` and `$self->config->classes` and sorts
by class index (lower = more urgent) then by priority index (higher
= most urgent), matching kanban-md's `internal/board/pick.go:74-90`.
`App::karr::Config->priority_order` and `->class_order` are removed
rather than left in place: their only callers were the two bug sites,
and leaving them around invites a future caller to use the wrong one
again. The `=attr priority` / `=attr class` POD in `Task.pm` now
references the `Config/priorities` and `Config/classes` instance
methods.
- `karr edit --status X --release` no longer walks straight through the
`require_claim` guard and lands a card in a require_claim column with
no claim on it (ticket k150). `Cmd/Edit.pm` cleared the claim forty
lines after `apply_status_change` had already satisfied the guard with
that same claim, so `karr edit 1 --claim agent-a` followed by
`karr edit 1 --status in-progress --release` produced an unowned
in-progress card â a state `karr move` and `karr edit --status` both
refuse to create by any other route. `karr edit 2 --status in-progress
--claim agent-b --release` did it in one command. The fix has two
pieces: `--claim` and `--release` are rejected together at the flag
layer as a usage error (exit 2), matching kanban-md
(`cmd/edit.go:128-130`); and the `--release` block now runs before
`apply_status_change`, so the guard in
`Role::TaskMutation::apply_status_change` sees the post-release state
and refuses the status change, matching kanban-md's `validateEditPost`
firing after `applyFn` regardless of release
(`internal/board/mutate.go:442`). `--release` alone on a card already
in a require_claim column is intentionally left unchanged: it is the
same shape but outside the ticket's scope (kanban-md has the same
hole).
- karr-foundation no longer throws away a successful agent run because of
something it printed (ticket k160). The common-error scan ran on every
run before anything asked whether the run had worked, over the whole
transcript, against bare substrings â network, quota, credentials, 401,
403, 429, 503. An agent working a karr board prints the board, so a
backlog line reading "retry the network fetch on 503" matched, and so
did a diffstat of 403 changed lines. The drain aborted, the cards the
agent had just moved were credited to nobody, and the cooldown climbed
1m, 2m, 4m ⦠64m without ever resetting, because the next run printed
the same words: a healthy board throttled to one discarded run per hour.
What a run did is now asked before what it printed. A run that exited 0
and moved the board is progress whatever scrolled past, and is never
reclassified by its own output; the scan is evidence only where there is
none other, a run that moved nothing â which is what a rate-limited or
unauthenticated agent looks like. A pattern seen in a run that did move
the board is noted in `.karr.log` and otherwise ignored. The default
patterns are narrow to match: a symptom word counts next to a failure
word on the same line ("network error", "invalid credentials", "quota
exceeded"), never on its own, and an HTTP status only where something
adjacent marks it as one ("API error: 429", "429 Too Many Requests") â
not in a diffstat, a byte count, a line number or a commit hash. Genuine
failures reported by an agent that still exits 0 keep triggering the
backoff, which is what the scan is for. A board's own `error_patterns`
are unchanged: plain case-insensitive substrings.
- `.karr.state` no longer keeps a `last_error` from a run three cooldowns
ago sitting next to `last_exit: 0` with nothing to explain the pair
(ticket k160). `last_error` describes the last run and is dropped by the
next run that is not a common error. Where the pair is real â an agent
that reports a rate limit and still exits 0 â it is now said out loud:
`.karr.log` records "COMMON-ERROR rate limit â agent exited 0, run
discarded", and `karr-foundation --status` names the reason beside the
wait ("cooldown 240s (rate limit)").
- Fixed data loss when a pull could not write a ref (ticket k154). The
apply step of the reconciliation used an unretried ref write whose
failure nobody checked, so a ref whose `.lock` file was held â by
another karr mid-write, or left behind by one that was killed â was not
applied, while the `refs/karr-remote/` mirror was advanced as if it had
been. The next reconciliation then read the stale local ref as unpushed
work and the forced, pruning push wrote it over the remote's newer card,
in every clone, at exit 0. Those writes now retry on the same terms as
every other ref write in `App::karr::Git`, a ref that still cannot be
applied leaves the mirror at its pre-fetch value so the next sync
decides it again, and the pull fails with a non-zero exit naming the ref
instead of proceeding to the push. The same fix covers a remote deletion
that could not be applied (which used to be pushed back as a
resurrection), a conflict whose local version could not be parked (the
local version is now kept rather than replaced), a mirror rollback
behind a refusal that only half succeeded (now reported), and the
board-identity stamp the mirror could not record.
- karr-foundation no longer auto-blocks tasks its agent never touched
(ticket k158). `_stuck_tasks` claimed to return "tasks the agent engaged
(claimed / in-progress) but did not move" and tested only whether the
card carried *any* claim or sat in `in-progress` â who held it was never
compared against anything. Every drain iteration in which the agent moved
some other card therefore charged an attempt against every card somebody
else was holding, and since `max_attempts` (default 2) can be spent
inside a single drain, a human's in-progress card was blocked with
`auto-block: no progress after N attempts (foundation)` and pushed to the
remote within seconds â a destructive write to shared board state about
work foundation never attempted, dropping that card out of `karr pick`'s
actionable set behind its owner's back and giving a reason that is
factually wrong. Engagement is now proven rather than assumed: foundation
runs the agent with `KARR_ROLE=agent`, so the agent's `karr` writes are
recorded in the board's own activity log under the `agent` identity, and
only cards named there during that drain â held by nobody, or under a
claim name the agent itself wrote with â can be penalized. A card the
agent merely left claimed in an earlier run no longer counts either; a
stale claim is what `claim_timeout` and `karr unlock` are for. Where that
evidence is missing altogether â an agent command that never calls
`karr`, an unreadable log â foundation now auto-blocks nothing rather
than guess: a drain that ends on its iteration cap costs an iteration,
blocking the wrong card costs somebody their work. The ownership test is
repeated at the write itself, which is the only place foundation mutates
a board, so a future caller inherits the guarantee instead of having to
remember it.
( run in 0.950 second using v1.01-cache-2.11-cpan-007c89162af )