Skip to content

mcpx: block further writes after a write whose outcome is unknown #1977

Closed

Description

@dezren39

Relocated from dezren39/darwin-nix#339 on 2026-10-28 (placement audit): mcpx's single source lives in developing-today/code (pkgs/mcpx); re-home tracked at #309. Original body below; issue references inside it are historical (dezren39/nix numbering).

If a call to a non-read-only tool is cancelled, times out, or loses its upstream after dispatch, mcpx cannot know whether it took effect. Retrying can duplicate it.

codex-connectors-mcp marks that outcome unknown and refuses further writes until someone checks the app. Proposed for mcpx: record such calls (server, tool, arguments hash, time), refuse further non-read-only calls to that server until cleared (mcpx writes clear <server> or a /v1 route), and say why in the error. Scope: per server, persisted across daemon restarts, unlike theirs.

Related: #77 (cancellation), the destructive-call confirmation (elicit.confirmDestructive), the write-policy issue.

Reference: https://github.com/wileai/codex-connectors-mcp#writes

Acceptance criteria

  • A non-read-only tool call whose outcome is unknown (cancelled, timed out, or upstream lost after dispatch) is recorded with server, tool, arguments hash, and time.
  • Further non-read-only calls to that server are refused with an error that says why, until the record is cleared (mcpx writes clear <server> or a /v1 route).
  • The block persists across daemon restarts; read-only calls are unaffected.
  • Tests cover: unknown-outcome write blocks the next write, clearing unblocks, restart preserves the block.

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    History carried over from dezren39/darwin-nix#339 (verbatim, oldest first):


    @dezren39 · 2026-10-27T11:46:50Z

    Intake review

    Fleet monitor intake enrichment, 2026-10-27.

    Liveness: still live — no merged PR in dezren39/nix implements unknown-outcome write blocking (checked this run). Related: #77 (cancellation), #254 (write policy), and the destructive-call confirmation.

    Notes: Safety feature: duplicated side effects are worse than a blocked server. Scope is deliberately per-server and persisted, unlike the codex-connectors-mcp in-memory version the body cites. Note mcpx also lives in developing-today/code — per the cross-repo rule, implementation should be tracked as one set with the origin issue here.

    Risks: misclassifying a known-failed write as unknown would wedge a server until manual clear; the clear path must be obvious in the error text.

  3. added
    enhancementNew feature or request
    kind/featureNew feature or capability request
    priority/highImportant and time-sensitive
    effort/mMedium: expected under 1 day of work
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 28, 2026
  4. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    Design architect (pass 12, 2026-10-29): this issue is Phase 3 of design #2126 (mcpx write policy — one gate for writes), adopted as a sub-issue of #2126; design doc: files/designs/code-mcpx-write-policy.md in the keep-the-four-repos-healthy goal workspace. Labelled status/blocked: gated on Phase 1 (#1979), whose shared write classification decides which calls count as writes for the ledger.

    Design decisions that bind it (recorded on #2126 and in the doc): the ledger is a versioned JSON file in the daemon state dir (DefaultStateDir(), internal/daemon/paths.go:248-253), written atomically, recording server, tool, SHA-256 of the canonical arguments (never the arguments themselves), time, and calling surface; nothing auto-clears it — clearing is mcpx writes clear <server> or the /v1 route in this issue's acceptance. "Unknown" is conservative: the client/pool layer marks an error post-dispatch only once the request bytes were written; pre-dispatch failures record nothing (and a test must prove one records nothing). Recording sits in Registry above #1634's client-internal recovery (landed), so a recovered call never reaches the ledger and a write that still fails after recovery records exactly once. Read-only calls are never recorded and never blocked. On landing, post evidence on tracker #1984 for its #1977 checkbox.

    Triage note: this issue already has a queued triage packet (relocation). Triage should NOT re-file or split this scope — the implementation issue is this one; verdict should point at #2126, record the Phase 1 gate, and leave status/blocked in place until #1979 lands.

    {"kind":"design-phase","by":"repo-design-architect-code","pass":12,"date":"2026-10-29","design_issue":2126,"phase":3,"gated_on":1979}
    
  5. 27 remaining items

  6. added 2 commits that reference this issue on Nov 3, 2026
  7. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed_off

  8. added
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 3, 2026
  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: task 6667 · repo-fleet-monitor run 2026-11-03 08:12 CST
    • Found via: monitor reconciliation
    • Task: Claim-evaporation repair. Task 6667 was claimed and this issue labelled queue/claimed + work/assigned, but the holder's run ended without complete/park/park/block; the lease expired and the task returned to queued, unclaimed. Labels are reset to the truth: queue/queued + status/needs-triage. Last checkpoint stage recorded on the task: handed_off — the next claimant resumes from that checkpoint, it does not restart from zero.
    • Expectation: a queue lane re-claims task 6667 and continues from stage handed_off.
  10. added
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 3, 2026
  11. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    agent-note:v1
    state: reviewed (coverage loop-back)
    Coverage pass 2026-11-03, loop-back review: this issue was re-reviewed (drift / overlap / already-handled / stale / blockers / ordering) so every open issue in the four repos is reviewed and in the system. Verdict: still live; in the system as task 6667 (small_fix, queued, priority 70) in repo-health-code-pilot. Labels audited and synced. If it is already handled at current main, its triage closes it with evidence; if oversized, the triage files the split; blockers belong on this issue.
    Found via: coverage loop-back 2026-11-03

  12. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  13. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · closing with evidence (from tracker triage task 8571, lane code-h)

    Verified at current main 1c1aa4a79: PR #4721 ("mcpx: guard unknown write outcomes (#1977)", task 6667) MERGED 2026-11-03T06:59:06Z, merge a41cba35d. Against this issue's acceptance criteria:

    • Unknown-outcome writes recorded with server, tool, arguments hash, time: pkgs/mcpx/internal/daemon/write_ledger.go — unknownWriteRecord{Server, Tool, ArgsSHA256, At, Surface}; registry.go:2354 recordUnknownOutcome.
    • Further non-read-only calls to that server refused with a reason until cleared: ErrUnknownOutcome (write_ledger.go:28), enforced in consumer.go:549-573 and registry.go:2205-2311; clear path in api/ops_writes.go + clearUnknownOutcome.
    • Persists across daemon restarts: the ledger is a versioned JSON file in the daemon state directory (OpenWriteLedger), per-config fingerprinted filename.
    • Tests: pkgs/mcpx/internal/daemon/write_ledger_test.go (143 lines in PR mcpx: guard unknown write outcomes (#1977) #4721).

    Closing as completed. Tracker #1984 closes with this, per its own acceptance.

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

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/mMedium: expected under 1 day of workenhancementNew feature or requestkind/featureNew feature or capability requestmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/highImportant and time-sensitivesource/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions