alibaba/spring-ai-alibaba · error · IllegalArgumentException
Human feedback metadata must be of type InterruptionMetadata
Error message
Human feedback metadata must be of type InterruptionMetadata.
What it means
HumanInTheLoopHook.interrupt() reads the human feedback payload from RunnableConfig under HUMAN_FEEDBACK_METADATA_KEY and requires it to be an InterruptionMetadata instance. If a resume carries feedback metadata of any other type, the hook cannot validate tool-call approvals and throws IllegalArgumentException instead of silently misinterpreting the data.
Source
Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/hook/hip/HumanInTheLoopHook.java:158
else {
log.warn("Last message is not an AssistantMessage, cannot process human feedback.");
}
return CompletableFuture.completedFuture(Map.of());
}
@Override
public Optional<InterruptionMetadata> interrupt(String nodeId, OverAllState state, RunnableConfig config) {
AssistantMessage lastMessage = getLastAssistantMessage(state);
if (lastMessage == null || !lastMessage.hasToolCalls()) {
return Optional.empty();
}
Optional<Object> feedback = config.metadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY);
if (feedback.isPresent()) {
if (!(feedback.get() instanceof InterruptionMetadata)) {
throw new IllegalArgumentException("Human feedback metadata must be of type InterruptionMetadata.");
}
if (!validateFeedback((InterruptionMetadata) feedback.get(), lastMessage.getToolCalls())) {
return buildInterruptionMetadata(state, lastMessage);
}
return Optional.empty();
}
// 2. If last message is AssistantMessage
return buildInterruptionMetadata(state, lastMessage);
}
private static AssistantMessage getLastAssistantMessage(OverAllState state) {
List<Message> messages = (List<Message>) state.value("messages").orElse(List.of());
AssistantMessage lastMessage = null;
for (int i = messages.size() - 1; i >= 0; i--) {
Message msg = messages.get(i);View on GitHub (pinned to f82da0b50f)
Solutions
- Construct an InterruptionMetadata (via its builder) carrying the approved/rejected tool call ids and put that object under HUMAN_FEEDBACK_METADATA_KEY instead of a Map or String.
- Check the value type before resuming: if (cfg.metadata(HUMAN_FEEDBACK_METADATA_KEY).map(v -> v instanceof InterruptionMetadata).orElse(true)) proceed.
- Review the HumanInTheLoopHook docs/examples for the current resume payload shape after upgrading versions.
Example fix
// before
config.metadata(HUMAN_FEEDBACK_METADATA_KEY, Map.of("approved", List.of("call-1")));
// after
InterruptionMetadata feedback = InterruptionMetadata.builder()
.approvedToolCallIds(List.of("call-1"))
.build();
config.metadata(HUMAN_FEEDBACK_METADATA_KEY, feedback); Defensive patterns
Strategy: type-guard
Validate before calling
Object fb = config.metadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY).orElse(null);
if (fb != null && !(fb instanceof InterruptionMetadata)) {
throw new IllegalStateException("Feedback metadata must be InterruptionMetadata, got " + fb.getClass());
} Type guard
boolean isValidFeedback(Object o) { return o == null || o instanceof InterruptionMetadata; } Try / catch
try {
resume(config);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("InterruptionMetadata")) {
// rebuild feedback payload as InterruptionMetadata and retry
} else throw e;
} Prevention
- Always build feedback via InterruptionMetadata.builder()
- Centralize resume-config construction in one helper
- Add a unit test asserting the metadata type placed on resume
When it happens
Trigger: Calling resume/continue on an interrupted graph while passing config metadata under key RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY with a value that is not an InterruptionMetadata object (e.g. a raw Map, String, or custom feedback DTO).
Common situations: Building custom UI resumption code that stuffs a hand-rolled feedback object into metadata; upgrading library versions where the feedback contract changed to InterruptionMetadata; copying older example code that used a plain map of approved/rejected tool call ids.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- extra tool param should be map
- Sub-agents must be BaseAgent
- Loop flow requires a valid LoopStrategy. Got: null
- Routing sub-agents must be BaseAgent for merge support
- Cannot merge incompatible types: {} and {}
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/1b068dc4c695437f.
Report an issue: GitHub.