#lobster workflow issue
1 messages · Page 1 of 1 (latest)
I checked the Lobster + /tools/invoke docs. I can’t see the actual workflow from cl1p here, so replace the path/args below with yours, but this is the manual shape.
First, sanity check version while debugging:
openclaw --version
openclaw status
If Lobster is enabled/allowed, run the workflow directly through the gateway:
export OPENCLAW_GATEWAY_TOKEN='your-token-here'
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tool": "lobster",
"action": "json",
"args": {
"action": "run",
"pipeline": "/absolute/path/to/workflow.lobster",
"argsJson": "{}",
"timeoutMs": 30000
}
}' | jq
For an inline pipeline instead of a .lobster file, make pipeline the command string:
"pipeline": "some.command --json | another.command --stdin json"
If it pauses for approval, grab requiresApproval.resumeToken from the output and resume manually:
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tool": "lobster",
"action": "json",
"args": {
"action": "resume",
"token": "PASTE_RESUME_TOKEN_HERE",
"approve": true
}
}' | jq
If you get 404, the usual cause is the lobster tool not being allowed. Docs recommend additive config like tools.alsoAllow: ["lobster"], not replacing the whole allowlist with only Lobster.
Docs:
https://docs.openclaw.ai/tools/lobster
https://docs.openclaw.ai/gateway/tools-invoke-http-api
Try checking again here https://cl1p.net/4564
The actual issue is whether I am correctly naming the model here
args.provider = process.env.LLM_TASK_PROVIDER || "lmstudio";
args.model = process.env.LLM_TASK_MODEL || "lmstudio/qwen/qwen3.6-35b-a3b";
I can see it now. The workflow is:
sql_lead_drafts_email_output, with the suspect step here:
- id: draft_email
command: sh -c 'node /home/admin/.openclaw/workspace-dba-scout/lobster-scripts/invoke-llm-task.js 2> /home/admin/.openclaw/workspace-dba-scout/debug-draft-email-stderr.txt'
stdin: $debug_llm_request.stdout
To run the whole Lobster workflow manually through OpenClaw, save that YAML as a file, for example:
/home/admin/.openclaw/workspace-dba-scout/sql-leads-email.lobster
Then invoke Lobster via the gateway:
openclaw --version
export OPENCLAW_GATEWAY_TOKEN='your-token-here'
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tool": "lobster",
"action": "json",
"args": {
"action": "run",
"pipeline": "/home/admin/.openclaw/workspace-dba-scout/sql-leads-email.lobster",
"timeoutMs": 30000,
"maxStdoutBytes": 512000
}
}' | jq
If you only want to debug the invoke-llm-task.js typo, run just the upstream input builder and that script:
cd /home/admin/.openclaw/workspace-dba-scout
python sql_leads/maintain_leads.py --dir sql_leads
cat sql_leads/active.md \
| node lobster-scripts/build-llm-request.js \
| tee debug-llm-request.json \
| node lobster-scripts/invoke-llm-task.js \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
Then inspect:
cat debug-draft-email-stderr.txt
jq . debug-draft-email.json
One thing to check: in the actual saved workflow file the redirect must be real shell syntax 2>, not HTML-escaped 2>. The cl1p page source shows 2> because it is HTML, but the text area may display it correctly.
Docs:
https://docs.openclaw.ai/tools/lobster
https://docs.openclaw.ai/gateway/tools-invoke-http-api
Yep, that looks likely wrong for llm-task.
In llm-task, provider and model are separate. The plugin builds the canonical key as:
provider + "/" + model
So with your current code:
args.provider = "lmstudio";
args.model = "lmstudio/qwen/qwen3.6-35b-a3b";
OpenClaw sees the model key as:
lmstudio/lmstudio/qwen/qwen3.6-35b-a3b
That is probably not what you configured.
Use the provider once, then the raw model id as LM Studio exposes it:
args.provider = process.env.LLM_TASK_PROVIDER || "lmstudio";
args.model = process.env.LLM_TASK_MODEL || "qwen/qwen3.6-35b-a3b";
But verify the exact model id from LM Studio instead of guessing:
curl -sS http://127.0.0.1:1234/v1/models | jq '.data[].id'
Whatever that prints is the value that should go in args.model, while args.provider stays lmstudio.
Also make sure your OpenClaw config has the same model id under models.providers.lmstudio.models[].id, and if llm-task.config.allowedModels is set, it should contain the full canonical form, for example:
"allowedModels": ["lmstudio/qwen/qwen3.6-35b-a3b"]
Docs:
https://docs.openclaw.ai/tools/llm-task
https://docs.openclaw.ai/gateway/local-models
I tried that but still doesn't work, how can I run it manually
Run it in layers so you can see exactly where it breaks.
First, get your version and confirm LM Studio’s actual model id:
openclaw --version
curl -sS http://127.0.0.1:1234/v1/models | jq '.data[].id'
Then test the model directly through LM Studio, using one of the ids printed above:
MODEL='qwen/qwen3.6-35b-a3b'
curl -sS http://127.0.0.1:1234/v1/chat/completions \
-H 'Content-Type: application/json' \
-d "{
\"model\": \"$MODEL\",
\"messages\": [
{ \"role\": \"user\", \"content\": \"Return exactly this JSON: {\\\"ok\\\":true}\" }
],
\"temperature\": 0
}" | jq
If that works, test OpenClaw’s llm-task directly:
export OPENCLAW_GATEWAY_TOKEN='your-token-here'
MODEL='qwen/qwen3.6-35b-a3b'
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"tool\": \"llm-task\",
\"action\": \"json\",
\"args\": {
\"provider\": \"lmstudio\",
\"model\": \"$MODEL\",
\"prompt\": \"Return a JSON object with ok true and source llm-task. No markdown.\",
\"schema\": {
\"type\": \"object\",
\"properties\": {
\"ok\": { \"type\": \"boolean\" },
\"source\": { \"type\": \"string\" }
},
\"required\": [\"ok\", \"source\"],
\"additionalProperties\": false
},
\"timeoutMs\": 60000
}
}" | jq
If that fails with 404, llm-task is not enabled/allowed. If it fails with “model not allowed”, your allowedModels needs the full key:
"allowedModels": ["lmstudio/qwen/qwen3.6-35b-a3b"]
If direct llm-task works, then run your workflow’s LLM part manually from the workspace:
cd /home/admin/.openclaw/workspace-dba-scout
python sql_leads/maintain_leads.py --dir sql_leads
cat sql_leads/active.md \
| node lobster-scripts/build-llm-request.js \
| tee debug-llm-request.json \
| LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='qwen/qwen3.6-35b-a3b' \
node lobster-scripts/invoke-llm-task.js \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
cat debug-draft-email-stderr.txt
jq . debug-draft-email.json
That last command is the closest manual reproduction of the failing Lobster step. If it still fails, paste the exact stderr plus openclaw --version and the model id printed by /v1/models.
llm-task args: {
"provider": "lmstudio",
"model": "qwen/qwen3.6-35b-a3b",
"timeoutMs": 1800000,
"maxTokens": 32000,
"temperature": 0,
"requestTimeoutMs": 2100000
}
Gateway HTTP request failed
URL: http://127.0.0.1:18789/tools/invoke
Status: 401 Unauthorized
Raw response:
{"error":{"message":"Unauthorized","type":"unauthorized"}}
That failure is before llm-task even runs. 401 Unauthorized from /tools/invoke means the Gateway rejected the bearer secret.
Check which auth mode your gateway is using:
openclaw --version
openclaw config get gateway.auth.mode
openclaw config get gateway.auth.token
openclaw config get gateway.auth.password
Do not paste the token/password here.
If mode is token, call it like:
export OPENCLAW_GATEWAY_TOKEN='paste-token-from-config-here'
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "qwen/qwen3.6-35b-a3b",
"prompt": "Return only {"ok":true}",
"timeoutMs": 1800000,
"maxTokens": 32000,
"temperature": 0
}
}' | jq
If mode is password, same header shape, but use the password value:
export OPENCLAW_GATEWAY_PASSWORD='paste-password-from-config-here'
-H "Authorization: Bearer $OPENCLAW_GATEWAY_PASSWORD"
If gateway.auth.token is empty/missing and you meant to use token auth, generate one:
openclaw doctor --generate-gateway-token
openclaw gateway restart
openclaw config get gateway.auth.token
Also: requestTimeoutMs is not listed as an llm-task parameter in the docs/source, so don’t rely on it. timeoutMs is the one llm-task uses.
Docs:
https://docs.openclaw.ai/gateway/tools-invoke-http-api
https://docs.openclaw.ai/tools/llm-task
{
"error": {
"message": "Expected ',' or '}' after property value in JSON at position 162 (line 7 column 32)",
"type": "invalid_request_error"
}
}
That one is my bad: the JSON body I gave had unescaped quotes inside the prompt string.
Use a heredoc so shell quoting doesn’t bite you:
export OPENCLAW_GATEWAY_TOKEN='paste-token-from-config-here'
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON' | jq
{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "qwen/qwen3.6-35b-a3b",
"prompt": "Return only a JSON object with ok set to true.",
"schema": {
"type": "object",
"properties": {
"ok": { "type": "boolean" }
},
"required": ["ok"],
"additionalProperties": false
},
"timeoutMs": 1800000,
"maxTokens": 32000,
"temperature": 0
}
}
JSON
If that returns 404, it’s tool enable/allowlist. If it returns a model/provider error, then auth is fixed and we’re back to model config.
That command just hangs
If it hangs, don’t keep testing with timeoutMs: 1800000 and maxTokens: 32000 yet. That can make a local model sit there for a long time.
First prove the gateway endpoint returns quickly with a short client timeout:
export OPENCLAW_GATEWAY_TOKEN='paste-token-from-config-here'
curl --max-time 10 -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON' | jq
{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "qwen/qwen3.6-35b-a3b",
"prompt": "Return only {"ok":true}.",
"timeoutMs": 10000,
"maxTokens": 20,
"temperature": 0
}
}
JSON
If that times out too, check the gateway log in another terminal while running it:
openclaw logs --follow
Also test LM Studio directly, bypassing OpenClaw:
MODEL='qwen/qwen3.6-35b-a3b'
curl --max-time 30 -sS http://127.0.0.1:1234/v1/chat/completions \
-H 'Content-Type: application/json' \
--data-binary @- <<JSON | jq
{
"model": "$MODEL",
"messages": [
{ "role": "user", "content": "Return only {\"ok\":true}." }
],
"max_tokens": 20,
"temperature": 0
}
JSON
If the direct LM Studio call hangs, the issue is LM Studio/model loading/server capacity, not OpenClaw. If direct LM Studio works but llm-task hangs, paste the relevant openclaw logs --follow lines plus openclaw --version.
One more thing: if your heredoc command seems to hang immediately without network output, make sure the final JSON marker is at the start of the line with no spaces before it. Otherwise the shell is still waiting for the heredoc to finish.
{
"error": {
"message": "Expected ',' or '}' after property value in JSON at position 150 (line 7 column 30)",
"type": "invalid_request_error"
}
}
That parse error means the body reaching /tools/invoke is still invalid JSON, likely from the quotes in the prompt. Use a prompt with no embedded JSON-looking quotes:
export OPENCLAW_GATEWAY_TOKEN='paste-token-from-config-here'
curl --max-time 20 -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON' | jq
{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "qwen/qwen3.6-35b-a3b",
"prompt": "Return one JSON object with a boolean field named ok set to true. No markdown.",
"timeoutMs": 10000,
"maxTokens": 20,
"temperature": 0
}
}
JSON
If you want to verify the JSON before sending it:
cat <<'JSON' | jq .
{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "qwen/qwen3.6-35b-a3b",
"prompt": "Return one JSON object with a boolean field named ok set to true. No markdown.",
"timeoutMs": 10000,
"maxTokens": 20,
"temperature": 0
}
}
JSON
If jq . fails, the curl will fail too.
{
"ok": false,
"error": {
"type": "tool_error",
"message": "tool execution failed"
}
}
Good, this means auth + JSON parsing are fixed. tool_error is the gateway’s sanitized wrapper; the real reason should be in the gateway logs.
Run this in one terminal:
openclaw logs --follow
Then rerun the short llm-task curl in another terminal. Look for lines around llm-task, tools-invoke, model, or provider.
Also run these and paste the non-secret output:
openclaw --version
openclaw config get plugins.entries.llm-task
openclaw config get models.providers.lmstudio
curl -sS http://127.0.0.1:1234/v1/models | jq '.data[].id'
Do not paste tokens/passwords.
The common causes at this point are:
llm-taskis enabled but the model is not inallowedModelsmodels.providers.lmstudio.models[].iddoes not exactly matchqwen/qwen3.6-35b-a3b- LM Studio direct API works, but OpenClaw’s provider config points at the wrong
baseUrlor API mode - the local model returns non-JSON, so
llm-taskrejects it as invalid JSON
If you want one more isolating check, run LM Studio directly:
curl --max-time 30 -sS http://127.0.0.1:1234/v1/chat/completions \
-H 'Content-Type: application/json' \
--data-binary @- <<'JSON' | jq
{
"model": "qwen/qwen3.6-35b-a3b",
"messages": [
{ "role": "user", "content": "Return one JSON object with a boolean field named ok set to true. No markdown." }
],
"max_tokens": 20,
"temperature": 0
}
JSON
I've changed it to this
curl --max-time 20 -sS http://127.0.0.1:18789/tools/invoke
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN"
-H "Content-Type: application/json"
--data-binary @- <<'JSON' | jq
{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"prompt": "Return one JSON object with a boolean field named ok set to true. No markdown.",
"timeoutMs": 10000,
"maxTokens": 20,
"temperature": 0
}
}
JSON
and I can see model working but get this message
{
"ok": false,
"error": {
"type": "tool_error",
"message": "tool execution failed"
}
}
I’d change that back unless LM Studio’s /v1/models literally prints lmstudio/qwen/qwen3.6-35b-a3b.
For llm-task, this:
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b"
becomes the canonical key:
lmstudio/lmstudio/qwen/qwen3.6-35b-a3b
So it often causes allowedModels / provider-model lookup failures. The model field should match the raw LM Studio model id exactly:
curl -sS http://127.0.0.1:1234/v1/models | jq -r '.data[].id'
Then use exactly that string as args.model.
The real reason for tool execution failed is still only in the gateway logs. Run this while retrying the curl:
openclaw logs --follow
If you want to avoid waiting on log noise, immediately after the failed request try:
openclaw logs --tail 100 | rg -i 'llm-task|tools-invoke|lmstudio|model not allowed|invalid json|provider/model|failed'
My bet is one of these exact messages:
Model not allowed by llm-task plugin config: lmstudio/lmstudio/...provider/model could not be resolvedLLM returned invalid JSON- a provider/runtime error from the LM Studio call
Also paste these non-secret outputs if you can:
openclaw config get plugins.entries.llm-task
openclaw config get models.providers.lmstudio
curl -sS http://127.0.0.1:1234/v1/models | jq -r '.data[].id'
Don’t paste the gateway token/password.
No the model is working, I use lmstudio so I can see it processing
Right, if LM Studio visibly starts processing, then OpenClaw is reaching the model. The failure is probably after the model call returns.
llm-task is strict: it tries to parse the model’s final text as JSON. If the model returns anything like <think>..., markdown, prose, or an empty/truncated response, llm-task throws and /tools/invoke only shows the generic tool execution failed.
Two things to try:
curl --max-time 60 -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON' | jq
{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"prompt": "Return exactly this JSON and nothing else: {"ok":true}. Do not include thinking, markdown, explanation, or any text before or after the JSON. /no_think",
"timeoutMs": 60000,
"maxTokens": 200,
"temperature": 0
}
}
JSON
And watch the real error while it runs:
openclaw logs --follow
The log line matters here. If it says LLM returned invalid JSON, then the model is working but not returning clean JSON. For Qwen-style reasoning models, that often means it is emitting thinking text before the JSON; using /no_think, disabling reasoning in LM Studio, or switching to a non-reasoning/instruct variant usually fixes it.
If it says timeout/empty output/provider error, that points somewhere else. The sanitized tool_error alone doesn’t contain enough detail.
{
"error": {
"message": "Expected ',' or '}' after property value in JSON at position 190 (line 7 column 61)",
"type": "invalid_request_error"
}
}
That is still JSON syntax, not model behavior. The prompt string contains raw quotes around "ok", which makes the request body invalid.
Use this exact version with no embedded quotes in the prompt:
curl --max-time 60 -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON' | jq
{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"prompt": "Return one JSON object with a boolean field named ok set to true. Do not include thinking, markdown, explanation, or any text before or after the JSON. /no_think",
"timeoutMs": 60000,
"maxTokens": 200,
"temperature": 0
}
}
JSON
If you want to catch this before sending:
cat <<'JSON' | jq .
{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"prompt": "Return one JSON object with a boolean field named ok set to true. Do not include thinking, markdown, explanation, or any text before or after the JSON. /no_think",
"timeoutMs": 60000,
"maxTokens": 200,
"temperature": 0
}
}
JSON
Only run the curl once jq . accepts the body.
curl --max-time 60 -sS http://127.0.0.1:18789/tools/invoke
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN"
-H "Content-Type: application/json"
--data-binary @- <<'JSON' | jq
{
"tool": "llm-task",
"action": "json",
"args": {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"prompt": "Return one JSON object with a boolean field named ok set to true. Do not include thinking, markdown, explanation, or any text before or after the JSON. /no_think",
"timeoutMs": 60000,
"maxTokens": 200,
"temperature": 0
}
}
JSON
That JSON body is valid now. What output did that exact command return?
If it still returns:
{ "ok": false, "error": { "type": "tool_error", "message": "tool execution failed" } }
then the next step is definitely the gateway log. Run this in a second terminal before running the curl:
openclaw logs --follow | rg -i 'llm-task|tools-invoke|lmstudio|invalid json|empty output|model not allowed|provider/model|failed|error'
Then rerun the curl and paste the matching log lines.
Also, for one test, remove the duplicate provider from the model unless LM Studio’s /v1/models prints it exactly:
"provider": "lmstudio",
"model": "qwen/qwen3.6-35b-a3b"
Even if LM Studio processes the request, the post-run llm-task error could still be from OpenClaw’s configured model key / allowedModels check or from invalid JSON returned by the model. The log line will tell us which one.
{
"ok": true,
"result": {
"content": [
{
"type": "text",
"text": "{\n "ok": true\n}"
}
],
"details": {
"json": {
"ok": true
},
"provider": "lmstudio",
"model": "qwen/qwen3.6-35b-a3b"
}
}
Nice, that proves llm-task works and the correct resolved model is:
provider: "lmstudio"
model: "qwen/qwen3.6-35b-a3b"
So in invoke-llm-task.js, use:
args.provider = process.env.LLM_TASK_PROVIDER || "lmstudio";
args.model = process.env.LLM_TASK_MODEL || "qwen/qwen3.6-35b-a3b";
Now debug the actual workflow step with the same known-good model values:
cd /home/admin/.openclaw/workspace-dba-scout
python sql_leads/maintain_leads.py --dir sql_leads
cat sql_leads/active.md \
| node lobster-scripts/build-llm-request.js \
| tee debug-llm-request.json \
| LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='qwen/qwen3.6-35b-a3b' \
node lobster-scripts/invoke-llm-task.js \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
cat debug-draft-email-stderr.txt
jq . debug-draft-email.json
If that fails, the problem is inside invoke-llm-task.js or the generated debug-llm-request.json, not the model name. The first thing I’d inspect is whether that script is adding bad JSON, using the wrong auth/token, or passing requestTimeoutMs instead of only the supported timeoutMs.
If this solves the thread, please right-click the solution message -> Apps -> âś… Mark Solution so Answer Overflow indexes it.
ok, though I had to specify model as lmstudio/qwen/qwen3.6-35b-a3b to get it to work
Got it, thanks for the correction. In that case your script should mirror the working curl value:
args.provider = process.env.LLM_TASK_PROVIDER || "lmstudio";
args.model = process.env.LLM_TASK_MODEL || "lmstudio/qwen/qwen3.6-35b-a3b";
Interesting bit: the successful response still reported the resolved model as qwen/qwen3.6-35b-a3b, so OpenClaw/LM Studio may be normalizing that on the way through. For your workflow, trust the exact value that made the direct llm-task call return ok: true.
Next step is just to run the workflow script with that env override:
LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='lmstudio/qwen/qwen3.6-35b-a3b' \
node /home/admin/.openclaw/workspace-dba-scout/lobster-scripts/invoke-llm-task.js
If this fixed it, please mark the solution so Answer Overflow indexes the thread.
That is working, however the lobster workflow still fails and I see this in the error logs
13:52:16
warn
diagnostic
lane wait exceeded: lane=main waitedMs=198946 queueAhead=1 activeAhead=1 activeNow=0 queueBehind=0
13:52:18
warn
agent/embedded
reasoning-only assistant turn detected: runId=llm-task-1779972537870 sessionId=llm-task-1779972537870 provider=lmstudio/qwen/qwen3.6-35b-a3b — retrying 1/2 with visible-answer continuation
13:52:19
warn
agent/embedded
reasoning-only assistant turn detected: runId=llm-task-1779972537870 sessionId=llm-task-1779972537870 provider=lmstudio/qwen/qwen3.6-35b-a3b — retrying 2/2 with visible-answer continuation
13:52:20
warn
agent/embedded
reasoning-only retries exhausted: runId=llm-task-1779972537870 sessionId=llm-task-1779972537870 provider=lmstudio/qwen/qwen3.6-35b-a3b attempts=2/2 — surfacing incomplete-turn error
13:52:20
warn
tools-invoke
tool execution failed: Error: LLM returned empty output
13:52:34
warn
agent/embedded
reasoning-only assistant turn detected: runId=llm-task-1779972753849 sessionId=llm-task-1779972753849 provider=lmstudio/qwen/qwen3.6-35b-a3b — retrying 1/2 with visible-answer continuation
13:52:35
warn
agent/embedded
reasoning-only assistant turn detected: runId=llm-task-1779972753849 sessionId=llm-task-1779972753849 provider=lmstudio/qwen/qwen3.6-35b-a3b — retrying 2/2 with visible-answer continuation
13:52:36
warn
agent/embedded
reasoning-only retries exhausted: runId=llm-task-1779972753849 sessionId=llm-task-1779972753849 provider=lmstudio/qwen/qwen3.6-35b-a3b attempts=2/2 — surfacing incomplete-turn error
13:52:36
warn
tools-invoke
tool execution failed: Error: LLM returned empty output
That log is the key. The model call is happening, but the embedded runner is receiving only a reasoning/thinking block and no visible assistant text:
reasoning-only assistant turn detected
...
LLM returned empty output
llm-task can only parse visible final text, so the workflow fails even though LM Studio is busy.
For this workflow, force the LLM task to disable thinking and keep the final answer tiny/visible. In your invoke-llm-task.js, add/ensure these fields before calling /tools/invoke:
args.provider = process.env.LLM_TASK_PROVIDER || "lmstudio";
args.model = process.env.LLM_TASK_MODEL || "lmstudio/qwen/qwen3.6-35b-a3b";
args.thinking = process.env.LLM_TASK_THINKING || "off";
args.temperature = 0;
args.timeoutMs = 1800000;
args.maxTokens = 32000;
And in the prompt that build-llm-request.js creates, add a hard final-output instruction near the end:
/no_think
Return ONLY the requested JSON as visible final text.
Do not include reasoning, analysis, markdown fences, or commentary.
The first character of your response must be { and the last character must be }.
Then test just the failing step:
cd /home/admin/.openclaw/workspace-dba-scout
cat sql_leads/active.md \
| node lobster-scripts/build-llm-request.js \
| tee debug-llm-request.json \
| LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='lmstudio/qwen/qwen3.6-35b-a3b' \
LLM_TASK_THINKING=off \
node lobster-scripts/invoke-llm-task.js \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
If it still logs reasoning-only, disable reasoning/thinking for that model in LM Studio or try a non-reasoning instruct model for this JSON workflow. The lane wait exceeded line is secondary queue pressure; the actual failure is LLM returned empty output after reasoning-only retries.
Full text can be found here https://cl1p.net/4564
I can see the issue in the pasted invoke-llm-task.js.
This line is working against you:
// JSON-only local model runs are usually more reliable without explicit thinking controls.
delete args.thinking;
Your logs show the opposite for this model/workflow: Qwen is producing a reasoning-only turn, then llm-task gets empty visible output.
Change that block to force thinking off:
args.provider = process.env.LLM_TASK_PROVIDER || "lmstudio";
args.model = process.env.LLM_TASK_MODEL || "lmstudio/qwen/qwen3.6-35b-a3b";
args.maxTokens = 32000;
args.timeoutMs = Number(process.env.LLM_TASK_TIMEOUT_MS || args.timeoutMs || 600000);
args.temperature = Number(process.env.LLM_TASK_TEMPERATURE || 0);
args.thinking = process.env.LLM_TASK_THINKING || "off";
Also add the model-facing instruction to the prompt that build-llm-request.js creates, not just to the curl smoke test:
/no_think
Return ONLY the requested JSON as visible final text.
Do not include reasoning, analysis, markdown fences, or commentary.
The first character of your response must be { and the last character must be }.
Then run the exact failing segment with the override:
cd /home/admin/.openclaw/workspace-dba-scout
cat sql_leads/active.md \
| node lobster-scripts/build-llm-request.js \
| tee debug-llm-request.json \
| LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='lmstudio/qwen/qwen3.6-35b-a3b' \
LLM_TASK_THINKING=off \
node lobster-scripts/invoke-llm-task.js \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
If that still logs reasoning-only assistant turn detected, then LM Studio/model settings are overriding the request. In that case disable thinking/reasoning for this model in LM Studio or use a non-thinking instruct model for the JSON step.
The sessionKey: "agent:lead-scout:main" is probably not the root cause, but for debugging you can temporarily remove it from the payload to match the curl test more closely.
I added that, but it is still not working and now no errors in the log
If there are no gateway errors now, we need to stop treating it as an llm-task failure and find the exact failing Lobster step.
Run the pipeline one step at a time and check each debug file:
cd /home/admin/.openclaw/workspace-dba-scout
python sql_leads/maintain_leads.py --dir sql_leads
cat sql_leads/active.md \
| node lobster-scripts/build-llm-request.js \
| tee debug-llm-request.json \
| LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='lmstudio/qwen/qwen3.6-35b-a3b' \
LLM_TASK_THINKING=off \
node lobster-scripts/invoke-llm-task.js \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
echo "invoke exit=$?"
wc -c debug-draft-email.json debug-draft-email-stderr.txt
cat debug-draft-email-stderr.txt
jq . debug-draft-email.json
If jq . debug-draft-email.json succeeds, continue:
node lobster-scripts/extract-llm-json.js \
< debug-draft-email.json \
| tee debug-extracted-json.json
jq . debug-extracted-json.json
node lobster-scripts/render-email-html.js \
< debug-extracted-json.json \
| tee debug-rendered-email.json
jq . debug-rendered-email.json
node lobster-scripts/write-email-html-file.js \
< debug-rendered-email.json
ls -lh sql-leads-draft-output.html
The first command that fails is the real current problem.
Also add one stderr line after the gateway response parse in invoke-llm-task.js, right before process.stdout.write(text);:
console.error("llm-task response bytes:", Buffer.byteLength(text));
That tells us whether the script is receiving a response but the later parser/render step dislikes it, versus the request still hanging. Paste the first failing command’s stderr/stdout shape, not tokens.
It just hangs after ouputing this from the maintenance
wc -c debug-draft-email.json debug-draft-email-stderr.txt
cat debug-draft-email-stderr.txt
jq . debug-draft-email.json
Lead file maintenance complete.
active_cutoff_date: 2026-05-21 (7 days)
archive_cutoff_date: 2026-03-29 (60 days)
active leads kept: 10
expired/moved from active: 30
processed/sent/rejected leads: 0
dedupe archive rows removed: 0
summary rows rebuilt: 10
oldest remaining active lead_date: 2026-05-21
verified: no remaining active lead is older than active_cutoff_date
That output means the maintenance step finished. The shell is hanging before it reaches wc -c ..., so it’s stuck in this part of the pipeline:
cat sql_leads/active.md |
node lobster-scripts/build-llm-request.js |
node lobster-scripts/invoke-llm-task.js
Split it into files so we can see which command is waiting:
cd /home/admin/.openclaw/workspace-dba-scout
python sql_leads/maintain_leads.py --dir sql_leads
timeout 30s node lobster-scripts/build-llm-request.js \
< sql_leads/active.md \
> debug-llm-request.json
echo "build exit=$?"
wc -c debug-llm-request.json
jq . debug-llm-request.json | head -80
If that works, run the invoke step by itself with a shorter timeout:
timeout 120s env \
LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='lmstudio/qwen/qwen3.6-35b-a3b' \
LLM_TASK_THINKING=off \
LLM_TASK_TIMEOUT_MS=60000 \
INVOKE_HTTP_TIMEOUT_MS=90000 \
node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
echo "invoke exit=$?"
wc -c debug-draft-email.json debug-draft-email-stderr.txt
cat debug-draft-email-stderr.txt
jq . debug-draft-email.json
While that second command is running, in another terminal:
tail -f debug-draft-email-stderr.txt
openclaw logs --follow | rg -i 'llm-task|tools-invoke|reasoning-only|empty output|failed|timeout|lmstudio'
My read: because invoke-llm-task.js redirects stderr into debug-draft-email-stderr.txt, you won’t see its “llm-task args” line until the process exits unless you tail the file. The split commands will tell us whether it’s hanging before the HTTP request, inside /tools/invoke, or waiting on LM Studio.
The first step completes. The second step hangs and I am not seeing lmstudio processing
If step 2 hangs and LM Studio never starts processing, the request is probably not reaching the model. Two likely causes in your pasted script:
invoke-llm-task.jsis stuck before/inside the HTTP call.- The explicit
sessionKey: "agent:lead-scout:main"is putting the tool call onto a busy/blocked lane.
First, remove this line from the payload for debugging:
sessionKey: process.env.OPENCLAW_SESSION_KEY || "agent:lead-scout:main",
So the payload becomes:
const payload = {
tool: "llm-task",
action: "json",
args
};
Then add these probes around the request:
console.error("posting to:", url);
console.error("has token:", Boolean(token));
console.error("requestTimeoutMs:", requestTimeoutMs);
const res = await postJson(url, payload, headers, requestTimeoutMs);
console.error("gateway status:", res.status, res.statusText);
console.error("gateway response bytes:", Buffer.byteLength(res.text || ""));
Run it again with a short hard timeout:
timeout 120s env \
LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='lmstudio/qwen/qwen3.6-35b-a3b' \
LLM_TASK_THINKING=off \
LLM_TASK_TIMEOUT_MS=60000 \
INVOKE_HTTP_TIMEOUT_MS=90000 \
node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
echo "invoke exit=$?"
cat debug-draft-email-stderr.txt
If you see posting to... but no gateway status..., it is waiting on /tools/invoke. In that case check the gateway queue/logs:
openclaw logs --tail 200 | rg -i 'lane wait|llm-task|tools-invoke|lead-scout|queue|timeout|failed'
The fact that direct curl worked but this script hangs points strongly at either the sessionKey difference or the generated debug-llm-request.json being much larger/slower than the smoke test. Removing sessionKey makes the script match the working curl much more closely.
Can you give me the full script here https://cl1p.net/4565
I put the full script here: https://cl1p.net/4565
Main changes from your pasted version:
- no hardcoded
sessionKey; it only usesOPENCLAW_SESSION_KEYif you explicitly set it - keeps
args.thinking = "off"by default - logs
posting to,has bearer auth, gateway status, and response bytes - supports either
OPENCLAW_GATEWAY_TOKENorOPENCLAW_GATEWAY_PASSWORD
Run it like:
cd /home/admin/.openclaw/workspace-dba-scout
timeout 120s env \
LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='lmstudio/qwen/qwen3.6-35b-a3b' \
LLM_TASK_THINKING=off \
LLM_TASK_TIMEOUT_MS=60000 \
INVOKE_HTTP_TIMEOUT_MS=90000 \
node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
cat debug-draft-email-stderr.txt
If it prints posting to... but never prints gateway status..., it’s waiting inside /tools/invoke. If it prints a gateway status, the next line tells us what came back.
that worked
Good, that means the culprit was likely the hardcoded sessionKey routing the invoke into a blocked/busy lane, with the reasoning setting as a secondary fix.
For the Lobster workflow, keep the updated invoke-llm-task.js and rerun the full workflow. If it completes, please right-click the solution message -> Apps -> âś… Mark Solution so Answer Overflow indexes the fix for the next person.
i rerun workflow. it failed and now that stderr file has this
llm-task args: {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"thinking": "low",
"timeoutMs": 600000,
"maxTokens": 8000,
"temperature": 0,
"requestTimeoutMs": 900000,
"sessionKey": null
}
posting to: http://127.0.0.1:18789/tools/invoke
has bearer auth: true
gateway status: 500 Internal Server Error
gateway response bytes: 76
Gateway HTTP request failed
URL: http://127.0.0.1:18789/tools/invoke
Status: 500 Internal Server Error
Raw response:
{"ok":false,"error":{"type":"tool_error","message":"tool execution failed"}}
The stderr shows the workflow is not using the same settings as the working manual test:
"thinking": "low"
That means debug-llm-request.json is carrying thinking: "low", and the script preserved it. For this model, that can trigger the reasoning-only/empty-output failure again.
Change these lines in invoke-llm-task.js so env/defaults override the generated request:
args.thinking = process.env.LLM_TASK_THINKING || "off";
args.maxTokens = Number(process.env.LLM_TASK_MAX_TOKENS || 32000);
args.timeoutMs = Number(process.env.LLM_TASK_TIMEOUT_MS || args.timeoutMs || 600000);
args.temperature = Number(process.env.LLM_TASK_TEMPERATURE || 0);
Important part: do not use args.thinking || "off" there, because the generated "low" will win.
Then rerun the workflow with an explicit env override if Lobster lets you pass env, or just rely on the script default after the edit:
LLM_TASK_THINKING=off LLM_TASK_MAX_TOKENS=32000 ...
Also check the gateway log for the 500’s real cause:
openclaw logs --tail 100 | rg -i 'llm-task|reasoning-only|empty output|invalid json|tool execution failed|lmstudio'
My bet is the log is back to reasoning-only or LLM returned empty output, because the workflow sent thinking: low while the manual passing test used off.
I can see this in the log file
15:05:58
warn
tools-invoke
tool execution failed: Error: Thinking level "low" is not supported for lmstudio/qwen/qwen3.6-35b-a3b. Use one of: off.
15:05:58
error
[tools] lobster failed: workflow command failed (1): sh -c 'node /home/admin/.openclaw/workspace-dba-scout/lobster-scripts/invoke-llm-task.js 2> /home/admin/.openclaw/workspace-dba-scout/debug-draft-email-stderr.txt' raw_params={"action":"run","pipeline":"/home/admin/.openclaw/workspace-dba-scout/sql-leads-draft.lobster","timeoutMs":1800000,"maxStdoutBytes":512000}
15:05:58
error
[tools] lobster failed: workflow command failed (1): sh -c 'node /home/admin/.openclaw/workspace-dba-scout/lobster-scripts/invoke-llm-task.js 2> /home/admin/.openclaw/workspace-dba-scout/debug-draft-email-stderr.txt' raw_params={"action":"run","pipeline":"/home/admin/.openclaw/workspace-dba-scout/sql-leads-draft.lobster","timeoutMs":1800000,"maxStdoutBytes":512000}
15:06:07
warn
tools-invoke
tool execution failed: Error: Thinking level "low" is not supported for lmstudio/qwen/qwen3.6-35b-a3b. Use one of: off.
Yep, that confirms it exactly: the generated request is still sending thinking: "low", and this model only supports off.
Patch invoke-llm-task.js so it overwrites the incoming value unconditionally:
args.thinking = process.env.LLM_TASK_THINKING || "off";
Not this:
args.thinking = process.env.LLM_TASK_THINKING || args.thinking || "off";
because args.thinking is currently "low".
You can verify before rerunning Lobster:
cd /home/admin/.openclaw/workspace-dba-scout
node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
cat debug-draft-email-stderr.txt
The stderr must show:
"thinking": "off"
Once it does, rerun the Lobster workflow.
I ran it it just hung again
Ok, then we need to distinguish “hung before gateway”, “hung inside gateway queue”, and “hung waiting for LM Studio”.
Run the invoke step with a hard outer timeout and then show stderr:
cd /home/admin/.openclaw/workspace-dba-scout
timeout 120s node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
echo "exit=$?"
cat debug-draft-email-stderr.txt
wc -c debug-draft-email.json
While it is running, also run:
openclaw logs --follow | rg -i 'llm-task|tools-invoke|lane wait|reasoning-only|empty output|thinking level|timeout|lmstudio|failed'
The stderr must show "thinking": "off". If it still says "thinking": "low", the script edit did not take effect.
If stderr shows posting to... but never gateway status..., the gateway call is stuck. Since the smoke test works, try shrinking the generated request to prove the large prompt/schema is the trigger:
cat > /tmp/llm-task-small.json <<'JSON'
{
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"thinking": "off",
"prompt": "Return one JSON object with a boolean field named ok set to true. No markdown.",
"timeoutMs": 60000,
"maxTokens": 200,
"temperature": 0
}
JSON
timeout 120s node lobster-scripts/invoke-llm-task.js \
< /tmp/llm-task-small.json \
> /tmp/llm-task-small.out \
2> /tmp/llm-task-small.err
cat /tmp/llm-task-small.err
jq . /tmp/llm-task-small.out
If the small JSON works but debug-llm-request.json hangs, the issue is the generated request size/content or output size, not auth/model routing anymore. Then check:
wc -c debug-llm-request.json
jq 'keys' debug-llm-request.json
jq '.prompt | length' debug-llm-request.json
Paste those non-secret outputs plus the stderr.
I ran it again, nothing in the log files, jut this in the stderr file
llm-task args: {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"thinking": "off",
"timeoutMs": 600000,
"maxTokens": 8000,
"temperature": 0,
"requestTimeoutMs": 900000,
"sessionKey": null
}
posting to: http://127.0.0.1:18789/tools/invoke
has bearer auth: true
Can you paste the stderr contents? It looks like the message ended before the actual file text.
If the stderr file only contains the first lines like:
llm-task args: ...
posting to: ...
has bearer auth: true
and never prints:
gateway status: ...
then the Node script is waiting on the HTTP request to /tools/invoke.
Please run this exact check and paste the output:
cd /home/admin/.openclaw/workspace-dba-scout
timeout 30s node lobster-scripts/invoke-llm-task.js \
< /tmp/llm-task-small.json \
> /tmp/llm-task-small.out \
2> /tmp/llm-task-small.err
echo "exit=$?"
cat /tmp/llm-task-small.err
wc -c /tmp/llm-task-small.out
jq . /tmp/llm-task-small.out
Also confirm this line in the stderr says off, not low:
"thinking": "off"
That tells us the script is definitely stuck waiting for the HTTP response from /tools/invoke. It has not reached LM Studio, and it has not received a gateway status.
Two quick isolations:
- Test the same script with the tiny payload. If this works, the generated
debug-llm-request.jsonis the trigger.
cat > /tmp/llm-task-small.json <<'JSON'
{
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"thinking": "off",
"prompt": "Return one JSON object with a boolean field named ok set to true. No markdown.",
"timeoutMs": 60000,
"maxTokens": 200,
"temperature": 0
}
JSON
timeout 120s node lobster-scripts/invoke-llm-task.js \
< /tmp/llm-task-small.json \
> /tmp/llm-task-small.out \
2> /tmp/llm-task-small.err
echo "exit=$?"
cat /tmp/llm-task-small.err
jq . /tmp/llm-task-small.out
- Check the generated request size/content:
wc -c debug-llm-request.json
jq 'keys' debug-llm-request.json
jq '.prompt | length' debug-llm-request.json
Also add one more debug line before postJson(...) in the script:
console.error("payload bytes:", Buffer.byteLength(JSON.stringify(payload)));
If the tiny payload works but the full one hangs, reduce the generated prompt or schema. If the tiny payload also hangs through the script but your manual curl works, then the script/request path differs from the curl and we should compare the exact payload next.
The small payload works, but a large payload works too. The issue is the if it is called from the lobster pipeline it just hangs and I don't see lmstudio processing anything
That nails it: the script works standalone, small and large payloads work standalone, but it hangs only when called from the Lobster pipeline and LM Studio never sees traffic.
That means the hang is almost certainly re-entrancy/queueing: embedded Lobster is already running as a Gateway tool, then a shell step inside that Lobster run calls back into the same Gateway /tools/invoke endpoint for llm-task. The nested request waits behind the workflow that is waiting for the nested request. Classic self-deadlock.
So the fix is architectural, not another model-name tweak. Pick one of these:
-
Best for now: don’t call
/tools/invokefrom inside an embedded Lobster step. Replaceinvoke-llm-task.jswith a script that calls LM Studio directly athttp://127.0.0.1:1234/v1/chat/completions, then outputs the JSON shape your later steps expect. -
Or run Lobster outside the Gateway, using the standalone Lobster CLI. Then the workflow process is not occupying the Gateway tool lane, so its
invoke-llm-task.jscan call/tools/invokenormally. -
Or split the workflow: let Lobster run through
debug_llm_request, stop; runinvoke-llm-task.jsmanually/outside Lobster; then run the render/write steps.
For your current workflow, option 1 is the cleanest: keep Lobster deterministic and make the model step a normal local command instead of a nested OpenClaw tool call.
The evidence is:
- direct
invoke-llm-task.js < debug-llm-request.jsonworks - inside Lobster it prints
posting to: /tools/invoke
- Gateway logs show nothing and LM Studio sees nothing
- therefore the request is stuck before tool execution, not inside LM Studio or
llm-task
That also explains the earlier lane wait exceeded message.
I want lobster to work as described
I agree. Based on the Lobster docs, openclaw.invoke / Gateway tool invocation from a Lobster workflow is intended to work. Your repro strongly suggests a bug in embedded Lobster/Gateway scheduling, not your LM Studio config anymore.
Please grab:
openclaw --version
openclaw config get plugins.entries.lobster
openclaw config get plugins.entries.llm-task
Then file this as a GitHub issue: https://github.com/openclaw/openclaw/issues
Useful issue summary:
Embedded Lobster workflow deadlocks/hangs when a workflow command calls /tools/invoke for llm-task.
What works:
- invoke-llm-task.js works standalone with the same debug-llm-request.json
- small and large payloads both work standalone
- LM Studio processes requests when run outside Lobster
What fails:
- same script called from an embedded Lobster workflow prints:
posting to: http://127.0.0.1:18789/tools/invoke
has bearer auth: true
then hangs forever
- LM Studio never sees the request
- gateway logs show no llm-task/tool execution for the nested request
Expected:
- Lobster workflow can invoke OpenClaw tools as documented, or fails fast with a clear re-entrant invocation error.
Suspicion:
- /tools/invoke request is queued behind the currently-running Lobster tool call, while Lobster is waiting for that nested request to finish.
For now, the practical workaround is still to avoid calling /tools/invoke from inside the embedded Lobster run: call LM Studio directly in that workflow step, run standalone Lobster outside the Gateway, or split the workflow around the LLM step. But yes, as documented, this should not silently hang.
It worked fine last week, clearly another openclaw upgrade broke it
That’s a fair read. Given it worked last week and now the same nested /tools/invoke call hangs before LM Studio sees anything, this does look like a regression in OpenClaw’s embedded Lobster/tool scheduling path.
For a useful bug report, capture:
openclaw --version
openclaw status --all
openclaw config get plugins.entries.lobster
openclaw config get plugins.entries.llm-task
If you know the last working version, include that too. The strongest repro sentence is:
Standalone invoke-llm-task.js works with the same input, but the same script hangs only when executed as a Lobster workflow step. It prints "posting to /tools/invoke" and "has bearer auth: true"; LM Studio never receives the request; gateway logs show no nested llm-task execution.
Please file it here so it doesn’t get lost in Discord: https://github.com/openclaw/openclaw/issues
Short-term, if you need the workflow running today, the least invasive workaround is to make that Lobster step call LM Studio directly instead of going back through /tools/invoke. Not ideal, but it avoids the re-entrant Gateway path that appears broken.
That is too vague I want a proper error log
Fair. Here’s a proper capture recipe that will produce a useful error bundle instead of “it hangs”.
- Turn file logging up temporarily:
openclaw config set logging.level trace
openclaw gateway restart
If you don’t want trace, use debug, but for a scheduler/queue hang I’d use trace briefly.
- Run this capture from the workspace. It timestamps the repro, forces a 90s failure instead of an infinite hang, and saves stderr/stdout/log excerpts:
cd /home/admin/.openclaw/workspace-dba-scout
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
CAPTURE_DIR="/tmp/openclaw-lobster-repro-$STAMP"
mkdir -p "$CAPTURE_DIR"
echo "capture_dir=$CAPTURE_DIR" | tee "$CAPTURE_DIR/README.txt"
openclaw --version | tee "$CAPTURE_DIR/openclaw-version.txt"
openclaw status --all > "$CAPTURE_DIR/openclaw-status-all.txt" 2>&1
openclaw config get plugins.entries.lobster > "$CAPTURE_DIR/plugin-lobster.txt" 2>&1
openclaw config get plugins.entries.llm-task > "$CAPTURE_DIR/plugin-llm-task.txt" 2>&1
openclaw config get logging.level > "$CAPTURE_DIR/logging-level.txt" 2>&1
LOG_FILE="$(ls -t /tmp/openclaw/openclaw-*.log | head -1)"
echo "$LOG_FILE" > "$CAPTURE_DIR/log-file.txt"
cp "$LOG_FILE" "$CAPTURE_DIR/gateway-before.log" 2>/dev/null || true
timeout 90s openclaw logs --follow --json > "$CAPTURE_DIR/logs-follow.jsonl" 2>&1 &
LOG_PID=$!
timeout 90s env \
LLM_TASK_PROVIDER=lmstudio \
LLM_TASK_MODEL='lmstudio/qwen/qwen3.6-35b-a3b' \
LLM_TASK_THINKING=off \
LLM_TASK_TIMEOUT_MS=60000 \
INVOKE_HTTP_TIMEOUT_MS=75000 \
node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> "$CAPTURE_DIR/invoke.stdout.json" \
2> "$CAPTURE_DIR/invoke.stderr.txt"
echo "invoke_exit=$?" | tee "$CAPTURE_DIR/invoke-exit.txt"
sleep 2
kill "$LOG_PID" 2>/dev/null || true
cp "$LOG_FILE" "$CAPTURE_DIR/gateway-after.log" 2>/dev/null || true
{
echo "=== invoke stderr ==="
cat "$CAPTURE_DIR/invoke.stderr.txt"
echo
echo "=== invoke stdout bytes ==="
wc -c "$CAPTURE_DIR/invoke.stdout.json"
echo
echo "=== relevant gateway logs ==="
rg -i 'lobster|llm-task|tools-invoke|lane wait|queue|timeout|lmstudio|reasoning-only|tool execution failed|error' "$CAPTURE_DIR/logs-follow.jsonl" "$CAPTURE_DIR/gateway-after.log" || true
} | tee "$CAPTURE_DIR/summary.txt"
echo "Capture written to: $CAPTURE_DIR"
- Reset logging after capture:
openclaw config set logging.level info
openclaw gateway restart
Paste summary.txt plus invoke-exit.txt. If you file the GitHub issue, attach the whole /tmp/openclaw-lobster-repro-... directory after checking it doesn’t contain secrets. Don’t paste gateway tokens/passwords.
The key things this capture proves are:
- whether the child process reaches
posting to /tools/invoke
- whether
/tools/invokelogs a received/queued request - whether the request times out without execution
- whether Lobster is occupying the same lane while the nested request waits
But there aren't any errors in the error log
Right, and that’s exactly why this is a hang/deadlock capture, not an error-log capture. A deadlocked request often produces no error line because nothing throws: one thing is waiting for another thing that can’t start.
So the useful evidence is:
- child script stderr stops after
posting to /tools/invoke - no
gateway statusline ever appears - LM Studio sees no request
openclaw logs --follow --jsonduring the repro has no nestedllm-taskexecution- the command exits only because
timeout 90skills it, usually exit124
That’s a valid bug report. The “proper error log” is the absence of progress under a bounded timeout, plus timestamps.
If you want an even tighter proof, run these two commands and compare:
# outside Lobster: should complete
timeout 90s node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> /tmp/outside.out \
2> /tmp/outside.err
echo "outside_exit=$?"
cat /tmp/outside.err
Then run the full Lobster workflow with the same 90s timeout and capture stderr/logs. The contrast is the bug:
outside Lobster: gets gateway status and LM Studio processes
inside Lobster: stops after posting to /tools/invoke, no LM Studio request, no gateway tool execution, killed by timeout
Errors are great when something crashes. Here the failure mode is “no forward progress,” so the report should say hangs/deadlocks without logging an error.
Summary of this thread:
Initial ask was how to run the Lobster workflow manually. The manual run path is:
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON' | jq
{
"tool": "lobster",
"action": "json",
"args": {
"action": "run",
"pipeline": "/home/admin/.openclaw/workspace-dba-scout/sql-leads-draft.lobster",
"timeoutMs": 1800000,
"maxStdoutBytes": 512000
}
}
JSON
What we found:
- Gateway auth had to be fixed first;
401meant the bearer token/password was wrong or missing. llm-taskworks manually through/tools/invoke.- For this LM Studio/Qwen setup, the working values were effectively
provider: "lmstudio"and the model value that worked in their environment waslmstudio/qwen/qwen3.6-35b-a3b. - The workflow-generated request had
thinking: "low", but the model only supportsoff; the script must forceargs.thinking = process.env.LLM_TASK_THINKING || "off";. - The hardcoded
sessionKey: "agent:lead-scout:main"ininvoke-llm-task.jswas removed for debugging. - Standalone
invoke-llm-task.js < debug-llm-request.jsonworks, but the same script hangs when called from inside the embedded Lobster workflow.
Current likely issue:
- This now looks like a regression/deadlock in embedded Lobster calling back into the same Gateway
/tools/invokepath. The nested request hangs before LM Studio sees it, with no error log because it is waiting rather than throwing.
Best next step:
- File a GitHub issue with version,
openclaw status --all, plugin config, and the bounded-timeout repro. The useful repro statement is: “Standalone script works; the same script hangs only as a Lobster workflow step after printingposting to /tools/invoke; LM Studio receives no request; gateway logs show no nestedllm-taskexecution.”
Issue tracker: https://github.com/openclaw/openclaw/issues
Short-term workaround:
- Avoid nested
/tools/invokefrom embedded Lobster: call LM Studio directly in that workflow step, run standalone Lobster outside the Gateway, or split the workflow around the LLM step.
BUt this usually takes 15 mins
Good correction. If a normal successful run takes ~15 minutes, use a timeout above that for the full workflow capture, like 25 minutes:
timeout 1500s openclaw ...lobster run...
or for the script step:
timeout 1500s node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
But one distinction matters: if LM Studio does not start processing within the first minute or two, that is not “the model is slow.” That means the nested request has not reached LM Studio yet. For this bug report, capture both:
- a successful standalone run that takes ~15 minutes and shows LM Studio processing
- the Lobster-run version where it stops after
posting to /tools/invokeand LM Studio never starts
So use a long timeout for fairness, but still note whether LM Studio ever receives the request. That’s the key signal.
The above manual run just hangs too
If you mean the manual /tools/invoke -> lobster command, then yes, that is still the embedded Gateway Lobster path. It is not an independent/manual execution path; it reproduces the same problem.
There are three different “manual” levels here:
- Embedded Lobster via Gateway, still affected:
curl ... /tools/invoke { "tool": "lobster", ... }
- The LLM step script outside Lobster, which you said worked:
node lobster-scripts/invoke-llm-task.js < debug-llm-request.json
- Standalone Lobster CLI outside the Gateway, if installed. That is the real test for whether Lobster itself can run the workflow without occupying the Gateway tool lane:
cd /home/admin/.openclaw/workspace-dba-scout
lobster run /home/admin/.openclaw/workspace-dba-scout/sql-leads-draft.lobster
If lobster is not installed, then option 3 is unavailable until you install the standalone Lobster CLI. The curl /tools/invoke tool=lobster route is useful for reproducing the Gateway regression, but it is not a workaround.
Yep option 1 with the curl just hangs, the pipeline does the first couple of steps but hangs on the invoke
That is the clean repro then:
curl /tools/invoke -> lobster starts
Lobster runs initial shell steps
workflow reaches invoke-llm-task.js
invoke-llm-task.js posts back to /tools/invoke for llm-task
request hangs there; LM Studio does not receive it
So the failure is not the early workflow steps, not auth, and not LM Studio. It is the nested /tools/invoke from inside an embedded Lobster run.
For the issue report, phrase it as:
Regression: embedded Lobster deadlocks when a workflow step calls /tools/invoke.
Repro:
1. Run Lobster through Gateway /tools/invoke.
2. Workflow executes normal shell steps successfully.
3. A later shell step calls /tools/invoke for llm-task.
4. That nested HTTP request hangs indefinitely before tool execution; LM Studio sees no request.
5. Running invoke-llm-task.js standalone with the same stdin works.
That’s specific enough for maintainers to investigate the queue/scheduling path.
actually, on closer inspection running the pipeline from curl does work
That changes the diagnosis. If curl -> /tools/invoke -> lobster works, then embedded Lobster itself is not generally deadlocking.
The remaining difference is likely the context that starts the workflow from chat/agent versus your direct curl call. Compare these:
- direct curl works
- chat/agent-triggered Lobster hangs on the invoke step
That points at session/tool policy/queue context, not the workflow file itself.
For the bug report or next debug pass, capture the exact raw params/context for both runs:
Working:
curl /tools/invoke tool=lobster action=run pipeline=...
Failing:
same pipeline launched by agent/chat, reaches invoke-llm-task.js, hangs posting to /tools/invoke
The next useful test is to make the failing launch match the working curl as closely as possible:
- same
pipeline - same
timeoutMs - same
maxStdoutBytes - no
sessionKeyon the nestedllm-taskrequest - same gateway token/password
- same env values:
LLM_TASK_MODEL,LLM_TASK_THINKING=off
If curl works reliably, use that as the workaround for now and file the regression as “agent/chat-launched Lobster workflow hangs, direct Gateway curl run succeeds.”
So is there any more debuging I can do?
Yes. Since direct curl works but the agent/chat-triggered run hangs, debug the differences between launch contexts.
- Add this to
invoke-llm-task.jsbefore the HTTP request:
console.error("env snapshot:", JSON.stringify({
OPENCLAW_SESSION_KEY: process.env.OPENCLAW_SESSION_KEY || null,
OPENCLAW_GATEWAY_URL: process.env.OPENCLAW_GATEWAY_URL || null,
OPENCLAW_GATEWAY_PORT: process.env.OPENCLAW_GATEWAY_PORT || null,
LLM_TASK_PROVIDER: process.env.LLM_TASK_PROVIDER || null,
LLM_TASK_MODEL: process.env.LLM_TASK_MODEL || null,
LLM_TASK_THINKING: process.env.LLM_TASK_THINKING || null,
cwd: process.cwd()
}, null, 2));
console.error("payload bytes:", Buffer.byteLength(JSON.stringify(payload)));
- Run the working curl-launched Lobster flow and save stderr:
cp debug-draft-email-stderr.txt /tmp/lobster-curl-stderr.txt
- Run the failing agent/chat-launched flow and save stderr:
cp debug-draft-email-stderr.txt /tmp/lobster-chat-stderr.txt
- Compare them:
diff -u /tmp/lobster-curl-stderr.txt /tmp/lobster-chat-stderr.txt
The most suspicious differences are:
OPENCLAW_SESSION_KEY- model/thinking env values
- gateway URL/token source
- cwd
- payload bytes
- Also capture logs around the failing run only:
openclaw logs --follow --json > /tmp/lobster-chat-logs.jsonl
Then stop it after the hang and filter:
rg -i 'lobster|llm-task|tools-invoke|lane wait|queue|sessionKey|agent:|timeout|failed|lmstudio' /tmp/lobster-chat-logs.jsonl
If curl works and chat does not, the best bug report includes the diff -u plus the filtered logs. That gives maintainers something concrete: same workflow, different launch context, exact env/payload difference.
The files are the same apart from the chat one just hangs and there are no entries in the log file
That narrows it further: the child process reaches the HTTP call, but the Gateway does not log receiving/handling it in the chat-launched case. So debug the TCP/request boundary, not llm-task.
Add these probes inside postJson in invoke-llm-task.js:
req.on("socket", (socket) => {
console.error("http socket assigned");
socket.on("lookup", (err, address, family, host) => {
console.error("http lookup:", { err: err && err.message, address, family, host });
});
socket.on("connect", () => {
console.error("http socket connected");
});
socket.on("timeout", () => {
console.error("http socket timeout");
});
socket.on("close", (hadError) => {
console.error("http socket closed:", { hadError });
});
});
Then in the request options, force IPv4 and no connection reuse:
family: 4,
agent: false,
So the request options include:
const req = client.request(
{
protocol: url.protocol,
hostname: url.hostname,
family: 4,
port: url.port,
path: `${url.pathname}${url.search}`,
method: "POST",
agent: false,
headers: {
...headers,
"content-type": "application/json",
"content-length": Buffer.byteLength(body),
},
},
...
);
Now rerun the chat-triggered workflow and check stderr. Interpret it like this:
- no
http socket assigned-> Node is stuck before it even opens a socket socket assignedbut nosocket connected-> TCP connect to Gateway is stuck/refused somewheresocket connectedbut nogateway statusand no Gateway logs -> Gateway accepted the socket but is not processing the HTTP requestgateway statusappears -> we’re past the hang and back to tool execution
While it is hung, also run from another terminal:
curl --max-time 5 -v http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tool":"llm-task","action":"json","args":{"provider":"lmstudio","model":"lmstudio/qwen/qwen3.6-35b-a3b","thinking":"off","prompt":"Return {"ok":true}","maxTokens":20,"timeoutMs":10000}}'
If that terminal curl also hangs while the chat-launched Lobster is hung, the Gateway HTTP server/event loop is blocked by that chat-triggered run. If the terminal curl works, the stuck child process/socket details will tell us what’s different.
Can you write the whole file here https://cl1p.net/4570
Done: https://cl1p.net/4570
This version includes:
- socket probes: assigned / connected / timeout / close / errors
family: 4to force IPv4agent: falseandconnection: closeto avoid connection reuse- env snapshot + payload byte logging
- forced
thinking: "off" - no session key unless
OPENCLAW_SESSION_KEYis explicitly set
After you rerun the chat-triggered workflow, the important stderr lines are whether it reaches:
http socket assigned
http socket connected
http request body flushed
http response headers received
gateway status:
The last line it prints before hanging tells us exactly where the request stops.
The curl now fails, here is the stderr file
stdin bytes: 19327
env snapshot: {
"OPENCLAW_SESSION_KEY": null,
"OPENCLAW_GATEWAY_URL": null,
"OPENCLAW_GATEWAY_PORT": "18789",
"LLM_TASK_PROVIDER": null,
"LLM_TASK_MODEL": null,
"LLM_TASK_THINKING": null,
"cwd": "/home/admin"
}
llm-task args: {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"thinking": "off",
"timeoutMs": 600000,
"maxTokens": 8000,
"temperature": 0,
"requestTimeoutMs": 900000,
"sessionKey": null
}
payload bytes: 19448
posting to: http://127.0.0.1:18789/tools/invoke
has bearer auth: true
http request body bytes: 19448
http request writing body
http request end called
http socket assigned
http socket connected
http request body flushed
http response headers received: { statusCode: 500, statusMessage: 'Internal Server Error' }
http response chunk bytes: 76
http response end bytes: 76
gateway status: 500 Internal Server Error
gateway response bytes: 76
Gateway HTTP request failed
URL: http://127.0.0.1:18789/tools/invoke
Status: 500 Internal Server Error
Raw response:
{"ok":false,"error":{"type":"tool_error","message":"tool execution failed"}}
Good, this is a real failure now, not a socket hang. The request reaches the Gateway and the Gateway returns a tool error:
http socket connected
http response headers received: 500
{"ok":false,"error":{"type":"tool_error","message":"tool execution failed"}}
So the next evidence is the Gateway log line at the same timestamp. Run this immediately after the failed curl/script run:
openclaw logs --tail 200 | rg -i 'llm-task|tools-invoke|tool execution failed|empty output|invalid json|thinking|lmstudio|provider|model|timeout|error'
If that misses it, use the raw file:
LOG_FILE="$(ls -t /tmp/openclaw/openclaw-*.log | head -1)"
rg -i 'llm-task|tools-invoke|tool execution failed|empty output|invalid json|thinking|lmstudio|provider|model|timeout|error' "$LOG_FILE" | tail -80
Also one thing stands out: the script is running with:
cwd: "/home/admin"
while your workflow files live under:
/home/admin/.openclaw/workspace-dba-scout
That may matter if the prompt/input references relative paths. For the next standalone test, run it from the workflow dir:
cd /home/admin/.openclaw/workspace-dba-scout
node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
But the 500 itself is now a Gateway-side llm-task failure, so the log line should tell us whether it is invalid JSON, empty output, timeout, model config, or something else.
18:44:01
warn
tools-invoke
tool execution failed: Error: LLM returned invalid JSON
18:44:01
warn
tools-invoke
tool execution failed: Error: workflow command failed (1): sh -c 'node /home/admin/.openclaw/workspace-dba-scout/lobster-scripts/invoke-llm-task.js 2> /home/admin/.openclaw/workspace-dba-scout/debug-draft-email-stderr.txt'
Now we have the actual error:
LLM returned invalid JSON
So Gateway/Lobster/invoke are working. The model is returning visible text, but llm-task cannot parse it as JSON.
Because llm-task only returns the sanitized 500 here, the fastest debug is to bypass llm-task once and call LM Studio directly with the exact prompt from debug-llm-request.json, saving the raw model text.
Run:
cd /home/admin/.openclaw/workspace-dba-scout
jq -r '.prompt' debug-llm-request.json > /tmp/sql-leads-prompt.txt
jq -n --rawfile prompt /tmp/sql-leads-prompt.txt '{
model: "lmstudio/qwen/qwen3.6-35b-a3b",
messages: [
{
role: "user",
content: ($prompt + "
/no_think
Return ONLY valid JSON. No markdown fences, no commentary. The first character must be { or [.")
}
],
temperature: 0,
max_tokens: 8000
}' > /tmp/lmstudio-debug-request.json
curl -sS http://127.0.0.1:1234/v1/chat/completions \
-H 'Content-Type: application/json' \
--data-binary @/tmp/lmstudio-debug-request.json \
| tee /tmp/lmstudio-debug-response.json \
| jq -r '.choices[0].message.content' \
| tee /tmp/lmstudio-debug-content.txt
jq . /tmp/lmstudio-debug-content.txt
That final jq . /tmp/lmstudio-debug-content.txt is expected to show the parse problem. Look for:
<think>or reasoning text before the JSON- markdown fences like ```json
- prose before/after the JSON
- truncated JSON because
maxTokensis too low
- invalid escapes/newlines inside string fields
If it’s just wrapped in markdown/prose, fix extract-llm-json.js or make invoke-llm-task.js call LM Studio directly and extract JSON itself. If it’s truncated, raise maxTokens or shrink the requested output. If it includes thinking despite /no_think, use a non-reasoning instruct model for this workflow.
The curl output this
{
"subject": "SQL Optimization & Licensing Savings",
"leads": []
}
{
"subject": "SQL Optimization & Licensing Savings",
"leads": []
}
That output is valid JSON, but it looks like it was printed twice. If the actual model content is two JSON objects back-to-back, llm-task will reject it as invalid JSON because this is not one JSON value:
{ ... }
{ ... }
Confirm with:
wc -c /tmp/lmstudio-debug-content.txt
cat -n /tmp/lmstudio-debug-content.txt
jq . /tmp/lmstudio-debug-content.txt
If cat -n shows two objects, that’s the bug. Tighten the prompt in build-llm-request.js to say:
Return exactly one JSON object.
Do not repeat the JSON.
Do not include a second copy.
Do not include markdown or commentary.
The response must be parseable by JSON.parse as a single value.
Also, your shown JSON has "leads": []. If the workflow expected actual leads, then the prompt/input mapping may be wrong too, but the immediate llm-task failure is likely duplicate JSON objects.
73 /tmp/lmstudio-debug-content.txt
1
2
3 {
4 "subject": "SQL Optimization & Licensing Savings",
5 "leads": []
6 }
{
"subject": "SQL Optimization & Licensing Savings",
"leads": []
}
That file is actually one valid JSON object. The second copy you pasted is probably jq . /tmp/lmstudio-debug-content.txt re-printing the parsed object, not a duplicate in the file.
So the direct LM Studio prompt is valid, but it still doesn’t exactly match what llm-task sends. llm-task wraps your request like this:
You are a JSON-only function...
TASK:
<debug-llm-request.json .prompt>
INPUT_JSON:
<debug-llm-request.json .input>
Test that exact shape directly:
cd /home/admin/.openclaw/workspace-dba-scout
jq -r '
"You are a JSON-only function. Return ONLY a valid JSON value. Do not wrap in markdown fences. Do not include commentary. Do not call tools.
TASK:
"
+ .prompt
+ "
INPUT_JSON:
"
+ (.input // null | tostring)
+ "
/no_think
Return exactly one valid JSON object and nothing else."
' debug-llm-request.json > /tmp/llm-task-exact-prompt.txt
jq -n --rawfile prompt /tmp/llm-task-exact-prompt.txt '{
model: "lmstudio/qwen/qwen3.6-35b-a3b",
messages: [{ role: "user", content: $prompt }],
temperature: 0,
max_tokens: 8000
}' > /tmp/llm-task-exact-lmstudio-request.json
curl -sS http://127.0.0.1:1234/v1/chat/completions \
-H 'Content-Type: application/json' \
--data-binary @/tmp/llm-task-exact-lmstudio-request.json \
| tee /tmp/llm-task-exact-lmstudio-response.json \
| jq -r '.choices[0].message.content' \
| tee /tmp/llm-task-exact-content.txt
cat -n /tmp/llm-task-exact-content.txt
jq . /tmp/llm-task-exact-content.txt
If that parses, then the remaining difference is OpenClaw’s embedded runner/transport, not the model’s raw ability to produce JSON. If that fails, the shown content tells you exactly what llm-task is choking on.
I get this
1
That means /tmp/llm-task-exact-content.txt is empty or only whitespace. So with the exact llm-task-style prompt, LM Studio returned no visible content. That matches the earlier LLM returned empty output / invalid JSON behavior.
Check whether the response has reasoning or another field instead of .choices[0].message.content:
jq . /tmp/llm-task-exact-lmstudio-response.json | head -120
jq '.choices[0].message' /tmp/llm-task-exact-lmstudio-response.json
Also check the prompt size:
wc -c /tmp/llm-task-exact-prompt.txt
head -40 /tmp/llm-task-exact-prompt.txt
tail -40 /tmp/llm-task-exact-prompt.txt
My read now: the llm-task wrapper plus your generated prompt is causing this Qwen reasoning model to produce no visible answer. The simpler direct prompt works, but the exact wrapped prompt does not.
Practical fixes:
- simplify
debug-llm-request.jsonso.promptdoes not already contain a full system-style instruction block - move the leads data into
.inputand keep.promptshort - use a non-reasoning instruct model for this JSON step
- or skip
llm-taskand call LM Studio directly with the simpler prompt that already produced valid JSON
If you paste jq '.choices[0].message' /tmp/llm-task-exact-lmstudio-response.json, we can see whether the model put the answer somewhere weird or genuinely returned empty content.
The output can be found here https://cl1p.net/4571
This output explains the failure.
LM Studio is returning:
"content": "",
"reasoning_content": "The user wants ... Generating JSON. ```json { ..."
So the model is putting everything, including the would-be JSON, into reasoning_content, while the visible assistant content is empty. llm-task only parses visible final text, so it either sees empty output or invalid JSON. That is why Gateway reports:
LLM returned invalid JSON
This is no longer Lobster. It is the model/API response shape.
Fix options:
-
Best: disable reasoning/thinking output for this Qwen model in LM Studio so the final JSON appears in
message.content, notmessage.reasoning_content. -
Use a non-reasoning/instruct model for this JSON workflow step.
-
Stop using
llm-taskfor this model and call LM Studio directly from the Lobster step, reading from:
choice.message.content || choice.message.reasoning_content
then extract the JSON yourself.
For llm-task to work, this command must print actual JSON:
jq -r '.choices[0].message.content' /tmp/llm-task-exact-lmstudio-response.json
Right now it is empty, and that is the root cause.
Thread summary:
Initial question: how to run the Lobster workflow manually.
Manual run through Gateway:
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON' | jq
{
"tool": "lobster",
"action": "json",
"args": {
"action": "run",
"pipeline": "/home/admin/.openclaw/workspace-dba-scout/sql-leads-draft.lobster",
"timeoutMs": 1800000,
"maxStdoutBytes": 512000
}
}
JSON
Main debugging findings:
401 Unauthorizedwas Gateway auth, fixed by using the correct token/password bearer.- The
llm-taskmodel args that worked in this environment usedprovider: "lmstudio"andmodel: "lmstudio/qwen/qwen3.6-35b-a3b". - The workflow was passing
thinking: "low", but the model only supportsoff; the script should forceargs.thinking = process.env.LLM_TASK_THINKING || "off";. - The hardcoded
sessionKeywas removed for debugging. - Later logs showed the real failure was
LLM returned invalid JSON.
Final root cause found:
LM Studio/Qwen returned the generated answer in message.reasoning_content while message.content was empty. llm-task only parses visible content, so it saw empty/invalid output even though LM Studio had generated text.
Fix options:
- Best: disable reasoning/thinking output for that Qwen model in LM Studio so JSON appears in
message.content. - Use a non-reasoning/instruct model for this JSON workflow step.
- Or bypass
llm-taskfor this step and call LM Studio directly, readingmessage.content || message.reasoning_content, then extracting/parsing the JSON in the script.
If this resolved the original issue, please right-click the solution message -> Apps -> âś… Mark Solution so Answer Overflow indexes it.
But doesn't the invoke script disable reasoning?
It disables reasoning from OpenClaw’s side, yes:
args.thinking = process.env.LLM_TASK_THINKING || "off";
But that only tells OpenClaw/llm-task to request thinking: off. It does not guarantee LM Studio or that Qwen model will put the final answer in message.content.
Your raw LM Studio response showed the important bit:
"content": "",
"reasoning_content": "..."
So either LM Studio is still exposing the model’s reasoning channel despite the OpenClaw thinking: off request, or the OpenAI-compatible adapter is mapping the model output into reasoning_content anyway.
That means thinking: off is necessary, but not sufficient for this model/server combo. For llm-task to work, the final JSON must appear in:
choices[0].message.content
not only in:
choices[0].message.reasoning_content
So the fixes are still: disable reasoning in LM Studio/model settings, use a non-reasoning instruct model, or bypass llm-task and have your script read content || reasoning_content before extracting JSON.
ok, I've disabled it, how do I test?
Test in three layers.
- Verify LM Studio now puts text in
message.content:
cd /home/admin/.openclaw/workspace-dba-scout
curl -sS http://127.0.0.1:1234/v1/chat/completions \
-H 'Content-Type: application/json' \
--data-binary @/tmp/llm-task-exact-lmstudio-request.json \
| tee /tmp/lmstudio-after-disable-response.json \
| jq '.choices[0].message | {content, reasoning_content}'
You want content to contain the JSON, and reasoning_content to be null/empty/missing.
- Test
llm-taskdirectly:
node lobster-scripts/invoke-llm-task.js \
< debug-llm-request.json \
> debug-draft-email.json \
2> debug-draft-email-stderr.txt
cat debug-draft-email-stderr.txt
jq . debug-draft-email.json
You want no LLM returned invalid JSON, and jq should parse the output.
- Then rerun the Lobster workflow:
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON' | jq
{
"tool": "lobster",
"action": "json",
"args": {
"action": "run",
"pipeline": "/home/admin/.openclaw/workspace-dba-scout/sql-leads-draft.lobster",
"timeoutMs": 1800000,
"maxStdoutBytes": 512000
}
}
JSON
If step 1 still shows empty content and populated reasoning_content, LM Studio is still not disabled for the server/model instance that OpenClaw is calling.
All three worked
Great, then the final fix is confirmed: LM Studio/Qwen was returning the answer in reasoning_content instead of visible message.content. Disabling reasoning for that model made llm-task receive parseable JSON, and the Lobster workflow works again.
Please mark the solution so this gets indexed:
Right-click the solution message -> Apps -> âś… Mark Solution
No. Its not fixed. If I run the lobster from a prompt it still hangs. I don't see lmstudio processing and this is in the stderr file
stdin bytes: 19327
env snapshot: {
"OPENCLAW_SESSION_KEY": null,
"OPENCLAW_GATEWAY_URL": null,
"OPENCLAW_GATEWAY_PORT": "18789",
"LLM_TASK_PROVIDER": null,
"LLM_TASK_MODEL": null,
"LLM_TASK_THINKING": null,
"cwd": "/home/admin"
}
llm-task args: {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"thinking": "off",
"timeoutMs": 600000,
"maxTokens": 8000,
"temperature": 0,
"requestTimeoutMs": 900000,
"sessionKey": null
}
payload bytes: 19448
posting to: http://127.0.0.1:18789/tools/invoke
has bearer auth: true
http request body bytes: 19448
http request writing body
http request end called
http socket assigned
http socket connected
http request body flushed
You’re right, I overstated it. What’s fixed is the LM Studio reasoning/JSON issue. This new stderr is a different failure mode.
This line sequence is the key:
http socket connected
http request body flushed
# no response headers
# LM Studio never starts
That means the nested HTTP request reached the Gateway socket, but the Gateway never got as far as executing llm-task. So this is back to launch-context/queueing, specifically when Lobster is started “from a prompt”.
Current matrix:
- Direct
invoke-llm-task.js < debug-llm-request.json: works - Direct
curl /tools/invoke -> lobster: works
- Prompt/agent-started Lobster -> workflow shell step ->
/tools/invoke llm-task: hangs beforellm-task
That strongly points to a re-entrant scheduling bug in the agent/prompt-launched tool path. The nested /tools/invoke is waiting for the Gateway/tool lane while the prompt-triggered Lobster call is already occupying it.
Useful repro wording for GitHub:
Regression: Lobster workflow with nested /tools/invoke works when launched by external curl, but hangs when launched from an agent prompt.
The workflow reaches a shell step that posts to http://127.0.0.1:18789/tools/invoke for llm-task.
The child process logs:
http socket connected
http request body flushed
but never receives response headers. LM Studio never receives the request. Direct curl-launched Lobster and direct invoke-llm-task.js both work with the same payload.
Workaround for now: launch this workflow via external curl/CLI instead of asking the agent to run Lobster from a prompt, or make the workflow call LM Studio directly for that step. The prompt-launched nested tool path looks broken.
Can you help me file in githib issue
I can help draft it, but I can’t file GitHub issues on your behalf. Please file it here: https://github.com/openclaw/openclaw/issues
Suggested title:
Regression: prompt-launched Lobster workflow hangs on nested /tools/invoke, while curl-launched workflow works
Suggested issue body:
## Summary
A Lobster workflow that calls `/tools/invoke` from a shell step hangs when the Lobster workflow is launched from an agent prompt/chat.
The same workflow works when launched directly via external `curl /tools/invoke`, and the same nested invoke script works standalone with the same input.
This appears to be specific to the prompt/agent-launched Lobster execution context.
## Version
```bash
openclaw --version
Paste output here.
Workflow shape
The Lobster workflow runs initial shell steps successfully, then reaches:
- id: draft_email
command: sh -c 'node /home/admin/.openclaw/workspace-dba-scout/lobster-scripts/invoke-llm-task.js 2> /home/admin/.openclaw/workspace-dba-scout/debug-draft-email-stderr.txt'
stdin: $debug_llm_request.stdout
That script posts to:
```text
http://127.0.0.1:18789/tools/invoke
to invoke llm-task.
Expected behavior
Prompt-launched Lobster should behave like curl-launched Lobster: the nested /tools/invoke request should execute llm-task and return a response, or fail fast with a clear error if nested invokes are unsupported in that context.
## Actual behavior
When launched from an agent prompt/chat, the workflow reaches the invoke step and hangs. LM Studio never receives the request.
The stderr from the child process ends here:
```text
stdin bytes: 19327
env snapshot: {
"OPENCLAW_SESSION_KEY": null,
"OPENCLAW_GATEWAY_URL": null,
"OPENCLAW_GATEWAY_PORT": "18789",
"LLM_TASK_PROVIDER": null,
"LLM_TASK_MODEL": null,
"LLM_TASK_THINKING": null,
"cwd": "/home/admin"
}
llm-task args: {
"provider": "lmstudio",
"model": "lmstudio/qwen/qwen3.6-35b-a3b",
"thinking": "off",
"timeoutMs": 600000,
"maxTokens": 8000,
"temperature": 0,
"requestTimeoutMs": 900000,
"sessionKey": null
}
payload bytes: 19448
posting to: http://127.0.0.1:18789/tools/invoke
has bearer auth: true
http request body bytes: 19448
http request writing body
http request end called
http socket assigned
http socket connected
http request body flushed
No `http response headers received` line appears.
## What works
The same workflow works when launched directly with curl:
```bash
curl -sS http://127.0.0.1:18789/tools/invoke \
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON' | jq
{
"tool": "lobster",
"action": "json",
"args": {
"action": "run",
"pipeline": "/home/admin/.openclaw/workspace-dba-scout/sql-leads-draft.lobster",
"timeoutMs": 1800000,
"maxStdoutBytes": 512000
}
}
JSON
The same invoke script also works standalone:
```bash
cd /home/admin/.openclaw/workspace-dba-scout
node lobster-scripts/invoke-llm-task.js
< debug-llm-request.json \
debug-draft-email.json
2> debug-draft-email-stderr.txt
## Notes
Earlier debugging found and fixed a separate LM Studio/Qwen issue: the model was returning output in `reasoning_content` instead of `message.content`. Disabling reasoning in LM Studio fixed that. The remaining issue is different: in the failing prompt-launched Lobster path, LM Studio never receives the request at all.
## Suspected cause
Possible re-entrant scheduling/queue issue when an agent/prompt-launched Lobster tool call runs a shell step that calls back into the same Gateway `/tools/invoke` endpoint.
The nested HTTP request connects and flushes its body, but Gateway never returns response headers and the nested `llm-task` execution does not appear to start.
Before submitting, add your exact openclaw --version and any relevant openclaw status --all output with secrets removed.