@clawling/clawchat-plugin-openclaw 2026.8.14-1 → 2026.8.14-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -153,8 +153,13 @@ export function createOpenclawClawlingApiClient(opts) {
153
153
  // `X-Device-Id` is sent on every request so the server can correlate
154
154
  // activity back to this plugin instance. Callers may override via
155
155
  // `extra` (e.g. tests) but the default is the channel id.
156
+ //
157
+ // A blank token gets NO Authorization header at all: `Bearer ` with an
158
+ // empty credential is a zero-segment JWT that the backend logs as
159
+ // malformed-token 401 noise (the unauthenticated plugin-report call sent
160
+ // one on every gateway entry).
156
161
  return {
157
- authorization: `Bearer ${opts.token}`,
162
+ ...(opts.token.trim() ? { authorization: `Bearer ${opts.token}` } : {}),
158
163
  "x-device-id": CHANNEL_ID,
159
164
  ...extra,
160
165
  };
@@ -221,6 +221,28 @@ function decodeJwtStringClaim(token, claim) {
221
221
  * wins; the mismatch is logged. Also derives userId from `sub` when nothing is
222
222
  * configured, mirroring the `oid` fallback above.
223
223
  */
224
+ /**
225
+ * Reject an ownerUserId that is an unrendered provisioning-template
226
+ * placeholder — treat it as absent (fall through to the next source) instead
227
+ * of using it verbatim.
228
+ *
229
+ * 2026-08 production incident: a provisioner shipped the literal `usr_owner`
230
+ * (this repo's own test-fixture value) un-rendered; every affected agent
231
+ * passed the connect gate and hammered prod with `GET /v1/users/usr_owner`
232
+ * plus malformed-JWT retries for days. Dev/mock ids stay loose on purpose —
233
+ * only the known fixture literal and unmistakable template syntax
234
+ * ({{…}}, ${…}, <…>, %…%) are rejected.
235
+ */
236
+ function rejectPlaceholderOwnerUserId(value, source) {
237
+ if (!value)
238
+ return "";
239
+ const looksUnrendered = value === "usr_owner" || /[{}<>%]|\$\{/.test(value);
240
+ if (looksUnrendered) {
241
+ console.warn(`clawchat ownerUserId from ${source} looks like an unrendered template placeholder ("${value}"); ignoring it. Fix the provisioning template — without a real owner id (or an oid claim in the token) the agent stays in activation-wait instead of hammering the backend.`);
242
+ return "";
243
+ }
244
+ return value;
245
+ }
224
246
  function resolveUserId(token, configured) {
225
247
  const tokenSub = decodeJwtStringClaim(token, "sub");
226
248
  if (!tokenSub)
@@ -336,8 +358,8 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
336
358
  const token = readOptionalString(channel.token) || readEnvString(env, CLAWCHAT_TOKEN_ENV);
337
359
  const agentId = readOptionalString(channel.agentId) || readEnvString(env, CLAWCHAT_AGENT_ID_ENV);
338
360
  const userId = resolveUserId(token, readOptionalString(channel.userId) || readEnvString(env, CLAWCHAT_USER_ID_ENV));
339
- const ownerUserId = readOptionalString(channel.ownerUserId) ||
340
- readEnvString(env, CLAWCHAT_OWNER_USER_ID_ENV) ||
361
+ const ownerUserId = rejectPlaceholderOwnerUserId(readOptionalString(channel.ownerUserId), "channel ownerUserId") ||
362
+ rejectPlaceholderOwnerUserId(readEnvString(env, CLAWCHAT_OWNER_USER_ID_ENV), CLAWCHAT_OWNER_USER_ID_ENV) ||
341
363
  // Fall back to the access token's `oid` (owner) claim. A provisioner may
342
364
  // inject token + userId but omit ownerUserId; without it the connect gate
343
365
  // (hasOpenclawClawlingConnectCredentials) never passes and the plugin sits
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawling/clawchat-plugin-openclaw",
3
- "version": "2026.8.14-1",
3
+ "version": "2026.8.14-2",
4
4
  "description": "OpenClaw ClawChat channel plugin",
5
5
  "license": "MIT",
6
6
  "author": "CLAWLING PTE. LTD.",
package/src/api-client.ts CHANGED
@@ -337,8 +337,13 @@ export function createOpenclawClawlingApiClient(opts: ApiClientOptions): Opencla
337
337
  // `X-Device-Id` is sent on every request so the server can correlate
338
338
  // activity back to this plugin instance. Callers may override via
339
339
  // `extra` (e.g. tests) but the default is the channel id.
340
+ //
341
+ // A blank token gets NO Authorization header at all: `Bearer ` with an
342
+ // empty credential is a zero-segment JWT that the backend logs as
343
+ // malformed-token 401 noise (the unauthenticated plugin-report call sent
344
+ // one on every gateway entry).
340
345
  return {
341
- authorization: `Bearer ${opts.token}`,
346
+ ...(opts.token.trim() ? { authorization: `Bearer ${opts.token}` } : {}),
342
347
  "x-device-id": CHANNEL_ID,
343
348
  ...extra,
344
349
  };
package/src/config.ts CHANGED
@@ -349,6 +349,30 @@ function decodeJwtStringClaim(token: string, claim: string): string {
349
349
  * wins; the mismatch is logged. Also derives userId from `sub` when nothing is
350
350
  * configured, mirroring the `oid` fallback above.
351
351
  */
352
+ /**
353
+ * Reject an ownerUserId that is an unrendered provisioning-template
354
+ * placeholder — treat it as absent (fall through to the next source) instead
355
+ * of using it verbatim.
356
+ *
357
+ * 2026-08 production incident: a provisioner shipped the literal `usr_owner`
358
+ * (this repo's own test-fixture value) un-rendered; every affected agent
359
+ * passed the connect gate and hammered prod with `GET /v1/users/usr_owner`
360
+ * plus malformed-JWT retries for days. Dev/mock ids stay loose on purpose —
361
+ * only the known fixture literal and unmistakable template syntax
362
+ * ({{…}}, ${…}, <…>, %…%) are rejected.
363
+ */
364
+ function rejectPlaceholderOwnerUserId(value: string, source: string): string {
365
+ if (!value) return "";
366
+ const looksUnrendered = value === "usr_owner" || /[{}<>%]|\$\{/.test(value);
367
+ if (looksUnrendered) {
368
+ console.warn(
369
+ `clawchat ownerUserId from ${source} looks like an unrendered template placeholder ("${value}"); ignoring it. Fix the provisioning template — without a real owner id (or an oid claim in the token) the agent stays in activation-wait instead of hammering the backend.`,
370
+ );
371
+ return "";
372
+ }
373
+ return value;
374
+ }
375
+
352
376
  function resolveUserId(token: string, configured: string): string {
353
377
  const tokenSub = decodeJwtStringClaim(token, "sub");
354
378
  if (!tokenSub) return configured;
@@ -498,8 +522,11 @@ export function resolveOpenclawClawlingAccount(
498
522
  readOptionalString(channel.userId) || readEnvString(env, CLAWCHAT_USER_ID_ENV),
499
523
  );
500
524
  const ownerUserId =
501
- readOptionalString(channel.ownerUserId) ||
502
- readEnvString(env, CLAWCHAT_OWNER_USER_ID_ENV) ||
525
+ rejectPlaceholderOwnerUserId(readOptionalString(channel.ownerUserId), "channel ownerUserId") ||
526
+ rejectPlaceholderOwnerUserId(
527
+ readEnvString(env, CLAWCHAT_OWNER_USER_ID_ENV),
528
+ CLAWCHAT_OWNER_USER_ID_ENV,
529
+ ) ||
503
530
  // Fall back to the access token's `oid` (owner) claim. A provisioner may
504
531
  // inject token + userId but omit ownerUserId; without it the connect gate
505
532
  // (hasOpenclawClawlingConnectCredentials) never passes and the plugin sits