Skip to content

Take stuck work back out of Symphony's queue - #1

Merged
tptodorov merged 2 commits into
mainfrom
unschedule-stuck-work
Aug 14, 2026
Merged

Take stuck work back out of Symphony's queue#1
tptodorov merged 2 commits into
mainfrom
unschedule-stuck-work

Conversation

@tptodorov

Copy link
Copy Markdown
Owner

S refused to unschedule any ticket Symphony held a session for. That is right while an agent is running, but wrong for the two states where nothing is progressing — blocked (waiting for an operator) and retrying (backing off after failing to start). Those are precisely the tickets you need to pull back, fix, and put in again, and they were the ones being refused.

Why removing the label is enough

The previous reasoning was that unscheduling "would not stop the work, only make the dashboard disagree with what is happening". Checked against Symphony's orchestrator rather than assumed, that does not hold for either stuck state:

  • retrying — when the retry timer fires, Symphony re-fetches the issue and re-tests retry_candidate_issue?Issue.routable?(issue, required_labels). With the label gone that is false, so it falls through to release_issue_claim ("Issue left active states, removing claim").
  • blockedreconcile_blocked_issue_state has an explicit !issue_routable?release_issue_claim branch ("Blocked issue no longer routed to this worker").

Both re-read the required labels before acting, so the claim really is dropped.

running is still refused: no label change interrupts a turn already in progress. That needs stopping the session in Symphony, whose API is read-only apart from POST /api/v1/refresh, so devdash cannot do it for you. symphonyHasIt becomes symphonyIsWorkingOnIt to name the state it actually means.

Also in here

  • The long-form ? help still described the old rule and contradicted the new behaviour — corrected alongside the key table in the help, the in-app view, and the README.
  • The README marker table gains the grey scheduled marker it never listed.
  • A Stop hook (.claude/settings.json) builds, tests, and installs to ~/.local/bin only when both pass, so the binary on PATH matches the source. Its deploy path is machine-specific; .claude/settings.local.json is the untracked home for it if that becomes awkward.

Verification

  • gofmt -l . clean, go vet ./... clean, go test -race -cover ./... passes (53.1% of statements).
  • TestCannotUnscheduleWhileAnAgentIsRunning covers the refusal; TestCanUnscheduleStuckWork covers blocked, retrying, scheduled and no-session, asserting each removes the label and leaves the status untouched.
  • Confirmed against a live Symphony on port 10000 that /api/v1/state really does return a blocked array — worth checking, since Symphony's SPEC.md does not document one and the Go implementation does not emit it, so on that backend blocked would never appear.
  • The hook's failure path was exercised with a deliberate syntax error: it reported the compile error and left the previously installed binary untouched.

Not covered: the live dry-run harness (DEVDASH_DRYRUN_DIR=... go test -run TestDryRunSchedulePlans) needs JIRA credentials that were not available, so the plan for every real ticket has not been eyeballed.

🤖 Generated with Claude Code

tptodorov and others added 2 commits August 14, 2026 16:19
Unscheduling refused any ticket Symphony held a session for, on the grounds that
removing the label would not stop the work. That is true while an agent is running,
but not for the two states where nothing is progressing: blocked, waiting for an
operator, and retrying, backing off after failing to start. Those are exactly the
tickets that need taking back, fixed, and putting in again — and they were the ones
the toggle refused.

Symphony re-reads the required labels before it acts on either. A retry that fires
re-tests routability and drops the claim when the label is gone; blocked issues are
reconciled the same way, releasing anything no longer routed to the worker. So
removing the label really does stop the work rather than merely hiding it from the
dashboard, which was the objection.

Only a running agent is refused now, since no label change interrupts a turn
already in progress — that needs stopping the session in Symphony, which has no API
for it here. symphonyHasIt becomes symphonyIsWorkingOnIt to say which state it
means.

The long-form help still described the old rule, so it was corrected alongside the
key table. The README gains the grey scheduled marker it never listed, and a note
on what the toggle leaves alone: unrelated labels, and the status.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A change that only exists in the source tree is not usable: devdash is driven as a
real tool while it is being worked on, so the binary on PATH is what actually gets
exercised. Leaving the install as a manual step meant testing yesterday's build
without noticing.

A Stop hook runs the build and the tests, and installs to ~/.local/bin only if both
pass, so a broken binary never lands on PATH — a failure reports the compiler or
test output and leaves the last good build in place. Roughly a second and a half
per turn once Go's cache is warm.

It installs from the working tree rather than the go install @latest the README
documents, which resolves from the remote and would quietly deploy without the
changes being tested.

Note that the deploy path is specific to one machine. If this becomes awkward for
anyone else, .claude/settings.local.json is the untracked place for it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tptodorov
tptodorov merged commit 5575e71 into main Aug 14, 2026
5 checks passed
@tptodorov
tptodorov deleted the unschedule-stuck-work branch August 14, 2026 13:35
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.

1 participant