Doist / Doist/comms-cli

tdc channel threads fails with READ_ONLY for a read-only token, while sibling read commands succeed

Open
#51 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
0
Forks
0
Avg merge
6h 35m
Merged PRs (30d)
10

Description

Investigated and written by Claude Code on Dominique Jost's behalf, submitted from his account. Not manually authored by Dominique. (Same convention as #23.)

Summary

tdc channel threads fails with READ_ONLY when the CLI is authenticated in read-only mode, even though listing threads is a read operation and the token carries comms:channels:read and comms:content:read.

It is the only read subcommand that behaves this way. Two siblings in the same command group, channel list and channel members list, both succeed on the same token.

Version and environment

  • tdc 2.0.2 (@doist/comms-cli)
  • macOS (darwin 25.6.0), node v25.2.1

Auth state

$ tdc auth status
✓ Authenticated
  Email: dominique@doist.com
  Mode:  read-only (scope: comms:channels:read comms:content:read comms:messages:read user:read workspaces:read)

Reproduction

$ tdc channel threads id:CcZXsoctdNfxcA5cdKUSB --json
{"error":{"code":"READ_ONLY","message":"This CLI is authenticated in read-only mode. Re-run `tdc auth login` without --read-only to enable write operations.","hints":["Re-run: tdc auth login (without --read-only)"]}}

Fails identically with:

  • a bare channel ref (tdc channel threads CcZXsoctdNfxcA5cdKUSB --json)
  • an explicit workspace positional (tdc channel threads CcZXsoctdNfxcA5cdKUSB 69 --json)
  • --limit 30, --since, and with no flags at all

Expected

A list of threads in the channel. comms:channels:read or comms:content:read should cover it.

Only this one subcommand is affected

Same token, same session:

Command Result
tdc channel threads id:<id> --json READ_ONLY
tdc channel list 69 --json OK (same command group)
tdc channel members list id:<id> --json OK (same command group)
tdc thread view <id> --json OK
tdc inbox --json OK
tdc conversation list 69 --json OK
tdc users 69 --json OK
tdc user --json OK
tdc workspaces --json OK

So this is not the channel group being classified as writes wholesale. Its siblings are fine.

Where the gate might live

The blocked call consistently takes longer than a successful one:

  • channel threads (blocked): 1.35s, 1.43s
  • channel list (succeeds, hits the network): 1.05s

Because the rejection is slower than a successful round trip, it does not look like a local pre-flight check. That points toward the API rejecting the request and the CLI mapping the rejection onto READ_ONLY, rather than a client-side command classification. Not conclusive, since it could also be attempting a token refresh before a local gate, but the scope mapping on the threads-list endpoint seems like the first place to look.

Possibly a regression

In #23 (2026-06-15, tdc 1.5.0), tdc channel threads --json reached the API and failed on a Zod schema validation error for pinned. So at 1.5.0 the command was getting past auth and into response parsing. The READ_ONLY behavior appears to have arrived after that.

Caveat: I do not know whether the token in #23 was read-only, so this establishes that the command reached the API at 1.5.0, not conclusively that read-only tokens were accepted then. The v2.0.0 release notes only mention the node >=24 requirement (#44), so the gate does not appear to have been introduced there.

Impact

There is currently no read-only way to enumerate the threads in a channel from the CLI:

  • tdc inbox covers inbox threads only, not a given channel's threads.
  • The Comms MCP search-content is search rather than enumeration, so it cannot reliably list what is in a channel. (It also returns zero results when its channelIds filter is set, even for queries that match when unfiltered, so client-side filtering is needed.)

This blocks read-only automation that needs to find threads in a channel, for example refreshing per-quarter snippets and HQ thread IDs when squads spin up new threads.

Workaround

Use the Comms MCP search-content without the channelIds filter and filter on channelId client-side. This finds threads by title that tdc search does not return, but it is search, not enumeration, so coverage is not guaranteed.

Possibly unrelated, observed in the same session

tdc search and tdc mentions intermittently return:

{"error":{"code":"INTERNAL_ERROR","message":"fetch failed"}}

Both worked earlier in the same session with the same token, so this reads as transient rather than scope-related. Noted here so it is not lost; happy to split it into its own issue if useful.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the tdc channel threads command entry point and compare its request or authorization path with channel list and channel members list, which succeed with the same read-only token. Check the threads-list API interaction and how its rejection is mapped to READ_ONLY. Done means tdc channel threads enumerates channel threads in read-only mode while the existing sibling commands remain unaffected.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, authentication, cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.