Wang1rrr opened a new issue, #4778:
URL: https://github.com/apache/rocketmq-dashboard/issues/4778
## Summary
`rmqctl message query` and `message query-by-topic` hide the response's
completeness metadata in their default table output whenever `items` is
nonempty. An incomplete result and a complete result with the same rows produce
identical stdout and no warning on stderr. JSON output preserves the metadata.
## Environment
- `rocketmq-studio`, CLI source at
`4c697f07acde460e2344375cb1f82669f5b270fd`.
- Windows amd64, Go 1.27.1; unmodified production CLI.
- Synthetic loopback HTTP response shaped to the current tool schema; no
live Studio backend or RocketMQ broker.
## Reproduce
Build the CLI from the revision above (`cd rmqctl; go build -o
rmqctl-repro.exe .`). Save the following as `repro.py`, then run `python
repro.py ./rmqctl-repro.exe` with Python 3.9+. It uses isolated dummy
credentials, temporary local config, and an ephemeral 127.0.0.1 server. Nothing
connects to a real account or instance.
The server sends the normal response envelope:
```json
{"code":200,"message":"success","data":{"items":[{"msgId":"mock-message-1","topic":"mock-topic","storeTime":1,"size":1}],"resultMayBeTruncated":true,"skippedCount":5}}
```
<details>
<summary>Self-contained production CLI reproducer</summary>
```python
"""Observe current CLI table metadata loss, using only a temporary loopback
server."""
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
cli = str(Path(sys.argv[1]).resolve())
item = {"msgId": "mock-message-1", "topic": "mock-topic", "storeTime": 1,
"size": 1}
payload = {"items": [item], "resultMayBeTruncated": True, "skippedCount": 5}
requests = []
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
request =
json.loads(self.rfile.read(int(self.headers["Content-Length"])))
expected = {"instanceId": "mock-instance", "topicName":
"mock-topic", "limit": 1}
if request.get("name") == "rmq.message.query":
expected["key"] = "mock-key"
valid = (self.client_address[0] == "127.0.0.1" and self.path ==
"/api/mcp/tools/call"
and request.get("name") in ("rmq.message.query",
"rmq.message.query_by_topic")
and request.get("arguments") == expected)
if not valid:
self.send_error(400, "unexpected synthetic request")
return
requests.append(request)
body = json.dumps({"code": 200, "message": "success", "data":
payload}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *_args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
server.daemon_threads = True
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
env = {"SYSTEMROOT": os.environ.get("SYSTEMROOT", r"C:\Windows"),
"WINDIR": os.environ.get("WINDIR", r"C:\Windows"), "NO_PROXY":
"127.0.0.1,localhost",
"REPRO_AK": "test-ak", "REPRO_SK": "test-sk"}
try:
with tempfile.TemporaryDirectory(prefix="rmqctl-repro-", dir=Path.cwd())
as tmp:
assert Path(tmp).resolve().is_relative_to(Path.cwd().resolve())
config = Path(tmp) / "config.yaml"
config.write_text("currentContext: repro\ncontexts:\n repro:\n"
f" server:
http://127.0.0.1:{server.server_port}\n"
" credential:\n accessKeyRef:
env:REPRO_AK\n"
" secretKeyRef: env:REPRO_SK\n",
encoding="utf-8")
config.chmod(0o600)
for verb in ("query", "query-by-topic"):
outputs = []
for partial, fmt in ((True, "table"), (False, "table"), (True,
"json")):
payload.update(resultMayBeTruncated=partial, skippedCount=5
if partial else 0)
args = [cli, "--config", str(config), "--instance-id",
"mock-instance",
"--timeout", "5s", "message", verb, "--topic-name",
"mock-topic", "--limit", "1"]
if verb == "query":
args += ["--key", "mock-key"]
if fmt != "table":
args += ["--output", fmt]
result = subprocess.run(args, env=env, capture_output=True,
text=True, timeout=15)
assert result.returncode == 0, result.stderr
assert not result.stderr, result.stderr
outputs.append(result.stdout)
assert "mock-message-1" in outputs[0]
assert outputs[0] == outputs[1], "Partial and complete tables
differ"
assert json.loads(outputs[2]) == payload
print(f"{verb}: partial and complete tables are identical; JSON
preserves metadata")
print(outputs[0], end="")
assert len(requests) == 6
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
```
</details>
For both commands, the partial (`true/5`) and complete (`false/0`) fixtures
print the same table:
```text
MSGID SIZE STORETIME TOPIC
mock-message-1 1 1 mock-topic
```
All six CLI calls exit 0 with empty stderr. The JSON control preserves
`resultMayBeTruncated: true` and `skippedCount: 5`.
A separate expanded diagnostic also confirmed that nonempty `true/0`
provider-bounded results are indistinguishable from complete results. Empty
`false/0` and `true/0` responses instead use the current JSON fallback and
preserve the fields. These are schema-shaped controls, not captured provider
responses. YAML preserves the two completeness fields; no claim of full YAML
semantic validation is intended.
## Expected behavior
The default human-readable table should expose when the result may be
incomplete, while preserving the existing rows and structured JSON/YAML
contract. `skippedCount` is only the number omitted from the provider-bounded
result, not necessarily the total number of all unseen broker messages. In
particular, `resultMayBeTruncated=true` with zero skipped rows must not look
complete.
## Root cause and scope
`cmd/catalog.go` routes L1 table output to `renderTable`.
`cmd/catalog_renderers.go` extracts the `items` field and renders only those
rows. Nonempty responses discard sibling metadata; empty responses serialize
the original object.
The current `MessageQueryOutput` contract already supplies the completeness
fields for both message query tools. This observation does not require changes
to limits, provider queries, or the server contract.
## Related work and proposed discussion
[#4645](https://github.com/apache/rocketmq-dashboard/pull/4645) and
[#4646](https://github.com/apache/rocketmq-dashboard/issues/4646) address
generating truncation information in an older server output contract. This
report concerns the CLI dropping information that is already present in the
current response. Those proposals should be considered together when reviewing
overlap.
I plan a narrow CLI fix: emit a warning on stderr only for these two query
tools when the default table contains nonempty rows and resultMayBeTruncated is
true. Keep stdout rows, structured JSON/YAML, empty-result fallback, query
parameters and exit semantics unchanged. This makes incompleteness visible
without redesigning tables or adding a dependency. The new stderr diagnostic is
an intentional observable change; scripts that equate any stderr output with
failure may need to distinguish warnings. No implementation has been made at
the time of this report, and feedback on this scope is welcome.
## Verification limits
The two command paths were exercised through actual production CLI processes
with synthetic HTTP fixtures and independently checked. This is a
client-rendering observation, not Java/Broker end-to-end verification or a
completed fix. AI assistance was used for analysis and reproducer preparation;
outputs and scope were independently reviewed.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]