Troubleshooting

Common issues and fixes when developing with ActingWeb.

401 at /mcp

  • Cause: No authentication configured or provided. MCP is OAuth2-protected in production.

  • Fix: Configure OAuth2 (Google/GitHub) with .with_oauth(…). Unauthenticated requests should return 401 with WWW-Authenticate header. For local, temporarily allow open access or sign in via /www.

DynamoDB Local connection errors

  • Symptom: Timeouts or table not found.

  • Fix: Ensure DynamoDB Local is running and set env vars:

    export AWS_ACCESS_KEY_ID=local
    export AWS_SECRET_ACCESS_KEY=local
    export AWS_DEFAULT_REGION=us-east-1
    export AWS_DB_HOST=http://localhost:8000
    

Slow first request after startup

  • Explanation: The permission system compiles/caches trust types on first use.

  • Fix: This is initialized automatically during framework integration. If you still see warmups during requests, check logs for initialization errors.

Tools/prompts don’t appear in tools/list or prompts/list

  • Check that you decorated hooks correctly: - Tools: @app.action_hook(“name”) + @mcp_tool(…) - Prompts: @app.method_hook(“name”) + @mcp_prompt(…)

  • Verify unified access control isn’t filtering them out for the current peer.

Property changes not visible in Web UI

  • Hook returns None for GET hides the property completely.

  • Hook returns None for PUT/POST marks the property read-only.

Subscription Callbacks Not Delivered

  • Symptom: Property changes on the publisher are not reaching subscribers.

  • Causes and fixes:

    1. Circuit breaker is open: Check the circuit breaker status for the peer:

      from actingweb.fanout import FanOutManager
      manager = FanOutManager(actor)
      status = manager.get_circuit_breaker_status(peer_id)
      if status == "OPEN":
          # Peer is unavailable, reset if issue resolved
          manager.reset_circuit_breaker(peer_id)
      
    2. Peer URL unreachable: Verify the peer’s callback URL is accessible and responding.

    3. Trust not approved: Ensure the trust relationship is approved on both sides.

    4. Subscription target mismatch: Verify the subscription target matches what you’re publishing.

Unexpected Resync Callbacks

  • Symptom: Full resync callbacks triggered frequently instead of incremental diffs.

  • Causes and fixes:

    1. Network latency: If callbacks arrive out-of-order often, increase the gap timeout:

      app.with_subscription_processing(gap_timeout_seconds=10.0)  # Default is 5.0
      
    2. Burst updates: Rapid property changes can cause sequence gaps. Consider using suspension:

      actor.subscriptions.suspend(target="properties")
      # ... perform bulk updates ...
      actor.subscriptions.resume(target="properties")  # Sends single resync
      
    3. Publisher restart: After a publisher restarts, sequence numbers reset. Subscribers should handle resync gracefully.

Duplicate Callbacks Being Processed

  • Symptom: Same data processed multiple times in your @subscription_data_hook.

  • Causes and fixes:

    1. auto_sequence disabled: Ensure subscription processing is enabled:

      app.with_subscription_processing(auto_sequence=True)  # Default
      
    2. Using raw callback_hook: The raw @callback_hook("subscription") doesn’t deduplicate. Migrate to @subscription_data_hook.

    3. Multiple hook registrations: Check you haven’t registered the same hook twice.

Peer Data Not Cleaned Up After Trust Deletion

  • Symptom: RemotePeerStore data persists after trust relationship is deleted.

  • Causes and fixes:

    1. auto_cleanup disabled: Enable automatic cleanup:

      app.with_subscription_processing(auto_cleanup=True)  # Default
      
    2. Custom trust hook overriding: If you have a @trust_hook("delete"), ensure it doesn’t prevent default cleanup.

    3. Manual cleanup needed: For legacy data, manually clean up:

      from actingweb.remote_storage import RemotePeerStore
      store = RemotePeerStore(actor, peer_id)
      store.delete_all()
      

Subscriber Returning 429 (Too Many Requests)

  • Symptom: Publisher receives 429 responses when sending callbacks.

  • Causes and fixes:

    1. Pending queue full: The subscriber’s max_pending limit was exceeded:

      # On subscriber, increase if needed:
      app.with_subscription_processing(max_pending=200)  # Default is 100
      
    2. Processing too slow: The subscriber’s hook is blocking. Keep hooks fast or offload work.

    3. Publisher retry strategy: Implement exponential backoff when receiving 429:

      # FanOutManager handles retries automatically
      # For custom implementations, wait before retrying
      

Sequence Gaps Not Resolving

  • Symptom: Callbacks stuck in pending queue, never processed.

  • Causes and fixes:

    1. Gap timeout too long: Reduce the timeout to trigger resync sooner:

      app.with_subscription_processing(gap_timeout_seconds=3.0)
      
    2. Missing callbacks: The publisher may have lost callbacks. Manually trigger resync:

      # On publisher side:
      actor.subscriptions.resume(target="properties")
      
    3. Check pending state: Use devtest endpoints to inspect pending queue (devtest mode only):

      GET /{actor_id}/devtest/callback_state/{peer_id}/{subscription_id}