NAME

Claude::Agent::Query - Query iterator for Claude Agent SDK

SYNOPSIS

use Claude::Agent::Query;
use Claude::Agent::Options;

my $query = Claude::Agent::Query->new(
    prompt  => "Find all TODO comments",
    options => Claude::Agent::Options->new(
        allowed_tools => ['Read', 'Glob', 'Grep'],
    ),
);

# Blocking iteration
while (my $msg = $query->next) {
    if ($msg->isa('Claude::Agent::Message::Result')) {
        print $msg->result, "\n";
        last;
    }
}

DESCRIPTION

This module handles communication with the Claude CLI process and provides both blocking and async iteration over response messages.

============================================================================

CRITICAL: THIS SDK IS NOT THREAD-SAFE

============================================================================

The Query object and its internal state must only be accessed from a single thread at a time. The internal re-entrancy guard (_processing_message) uses a non-atomic check-then-set pattern that provides protection against recursive calls in single-threaded event loops only - it does NOT provide thread safety.

For multi-threaded applications, you MUST:

  • Use external synchronization (mutexes, locks) to protect ALL Query method calls, OR

  • Use separate Query instances per thread (no shared state)

Failure to synchronize access from multiple threads may result in message loss, ordering issues, or undefined behavior.

CONSTRUCTOR

my $query = Claude::Agent::Query->new(
    prompt  => "Find all TODO comments",
    options => $options,
    loop    => $loop,    # optional, for async integration
);

Arguments

  • prompt - Required. The prompt to send to Claude.

  • options - Optional. A Claude::Agent::Options object.

  • loop - Optional. An IO::Async::Loop for async integration. If not provided, a new loop is created internally.

Important: For proper async behavior, pass your application's event loop. This allows next_async to be truly event-driven instead of polling.

Thread Safety: This class is NOT thread-safe. All method calls must be protected by external synchronization when accessed from multiple threads.

next

my $msg = $query->next;

Blocking call to get the next message. Returns undef when no more messages.

Performance Note: This method uses a polling loop with 0.1 second intervals, which may cause unnecessary CPU wake-ups and latency for long-running queries. For better efficiency in async applications, use next_async() with Future->wait() or integrate with your event loop directly.

TIMEOUT BOUNDARY WARNING: The actual timeout may exceed the configured query_timeout value by up to 0.1 seconds (the polling interval), as the timeout check occurs after each loop_once() call completes. This is inherent to the polling design. For applications requiring precise timeout behavior:

  • Use next_async() with Future->wait_until($deadline) for precise control

  • Set query_timeout slightly lower than your hard deadline to account for skew

  • Integrate with your own event loop for microsecond-precision timing

next_async

my $msg = await $query->next_async;

Async call to get the next message. Returns a Future that resolves when a message is available. This is truly event-driven - no polling.

session_id

my $id = $query->session_id;

Returns the session ID once available (after init message).

is_finished

if ($query->is_finished) { ... }

Returns true if the query has finished (process exited).

error

if (my $err = $query->error) { ... }

Returns error message if the process failed.

interrupt

$query->interrupt;

Send interrupt signal to abort current operation.

send_user_message

$query->send_user_message("Continue with the next step");

Send a follow-up user message during streaming.

set_permission_mode

$query->set_permission_mode('acceptEdits');

Change permission mode during streaming.

respond_to_permission

$query->respond_to_permission($tool_use_id, {
    behavior      => 'allow',
    updated_input => $input,
});

Respond to a permission request.

rewind_files

$query->rewind_files;

Revert file changes to the checkpoint state.

AUTHOR

LNATION, <email at lnation.org>

LICENSE

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under The Artistic License 2.0 (GPL Compatible).