Skip to content

HITL Approve click never reaches server — approved stays false, overrides stay empty #3

@hanshenderickx

Description

@hanshenderickx

Bug: HITL Approve click never reaches server — approved stays false, overrides stay empty (affects both anonymize_file and anonymize_text sessions)

Summary

After calling start_review(session_id) and clicking Approve in the in-chat review panel, the panel displays a "Review approved" confirmation dialog client-side. However, the server never records the approval: subsequent calls to start_review still return approved: false and overrides: { remove: [], add: [] }, and anonymize_file(..., review_session_id=...) returns status: "waiting_for_approval" indefinitely.

This blocks the entire HITL pipeline for both file-based and text-based sessions.

Environment

Field Value
PII Shield version 2.0.2
Runtime Node.js v24.15.0
Host Claude Desktop → Cowork mode (local agent session)
OS macOS (Apple Silicon, Darwin)
Model gliner-pii-base-v1.0 (~634 MB), installed via in-chat setup panel
Data dir ~/.pii_shield (fallback)
NER status ner_ready: true, NER detection working correctly

Reproducer

Minimal sequence (reproduced multiple times in one session, in two different modes):

Mode A — file input

1. anonymize_file(file_path: "/path/to/contract.docx")
   → returns status: "success", session_id: "S1"
2. start_review(session_id: "S1")
   → returns status: "review_ready", approved: false, overrides empty
   → review panel renders in chat with entity highlights
3. User clicks Approve in the panel
   → panel shows "Review approved" dialog client-side
4. anonymize_file(file_path: "/path/to/contract.docx", review_session_id: "S1")
   → ACTUAL:    status: "waiting_for_approval" (loops indefinitely)
   → EXPECTED:  status: "success" or "approved_no_changes"

Mode B — direct text input

1. anonymize_text(text: "Dear HR, I am writing to report ... Sophie Vandenberghe ...")
   → returns status: "success", session_id: "S2", entity_count: 6
2. start_review(session_id: "S2")
   → returns status: "review_ready", approved: false, source_filename: "inline_text"
3. User clicks Approve in the panel
   → panel shows "Review approved" client-side
4. start_review(session_id: "S2")  (re-call to inspect server state)
   → ACTUAL:    approved: false, overrides: { remove: [], add: [] }
   → EXPECTED:  approved: true, overrides reflecting user's edits (or empty if no edits)

Observations

  • The bug affects both session types created by anonymize_file and anonymize_text, so it is not specific to the file-handling path.
  • The panel UI is rendered (entities highlighted, Approve button visible, post-click dialog shown), so the iframe is loading correctly.
  • The server-side state for the session never transitions to approved: true, which is the gating condition the skill's pipeline relies on.
  • The "Review approved" dialog the user sees client-side appears to be optimistic — the panel announces success before (or instead of) confirming the server actually persisted the override.
  • Re-opening the panel via start_review(session_id) on an already-"approved" session still returns approved: false — i.e., it's not a stale-cache issue in the panel; the server itself never saw the apply.

Likely cause (informed guesses, please verify)

The panel UI presumably calls apply_review_overrides(session_id, overrides) on the MCP server when the user clicks Approve. Most plausible failure modes:

  1. apply_review_overrides is called via a JS-bridge path that this host doesn't expose to MCP Apps iframes. The panel may be using postMessage to a parent frame that, in Cowork, isn't routing the call back to the MCP server process. The skill's own documentation flags an analogous limitation: "that tool call is invisible to Claude on some hosts (known limitation)" — so a known-bad signal path may also be the cause of the server not seeing it.
  2. The tool call is being made but to the wrong endpoint (e.g., a stdio MCP connection vs. an HTTP transport, or to a different session_id than the one in the panel state).
  3. The call is being made but failing silently. If apply_review_overrides returns an error and the panel doesn't surface it, the dialog might still show "approved" client-side.

Suggested instrumentation to verify

  1. Add a server-side log line at the top of apply_review_overrides handler — does the request ever arrive? Run with DEBUG=* or equivalent and check stdout/stderr captured by Claude Desktop's MCP logs.
  2. In the panel's Approve-click handler, log the result of the apply_review_overrides call (success/error) to the panel iframe's console. The user can then open dev tools on the panel iframe to capture it.
  3. Add a apply_review_overrides_attempted counter to list_entities output so users can see "the panel tried to apply N times" vs. "server received N applies", making it obvious where the signal is being lost.

Workaround used in this session

The skill defines a PII_SKIP_REVIEW=true environment variable that bypasses the HITL gate. Setting that lets the pipeline proceed without the review step, but obviously sacrifices the human-correction step that makes PII Shield trustworthy on real PII (the NER itself missed three person names and one mis-tagged date in our synthetic test text — exactly the kind of error the review step is designed to catch).

What's working well

So this isn't a "PII Shield is broken" report — it's specifically the panel↔server signal step:

  • File auto-resolution (BFS of Downloads/Documents/Desktop) — worked first try on a non-standard Claude Desktop session path.
  • GLiNER inference quality — caught 6 of 7 entity types correctly on a Belgian-context HR test message (email, location, IBAN, phone, ID number, plus one mis-fire on a date that the HITL step would catch).
  • Session model + placeholder coalescing — <EMAIL_1> appeared consistently for the same entity across multiple positions in the doc.
  • start_review panel rendering — the iframe loads, entities highlight correctly, buttons are interactive.
  • Model setup panel — clean one-click Download → one-click Install flow worked end-to-end.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions