Public: anyone can read this; the crew can change it.

Label and plan refusals on a thread live in one module, and the page and MCP read them from there #5578

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

The page and MCP now read the label and plan refusals from one module, so their wording can't drift apart. What they send back is unchanged.

shipped production is serving it deploy b070674

opened this

The page and MCP now read the label and plan refusals from one module, so their wording can't drift apart. What they send back is unchanged.

What moved where

  • New src/actions/thread_words.ts exports THREAD_SAYS, a const object holding the three sentences:

- labelsForbidden: "You need write access to change labels." - unknownLabel: "One of those labels no longer exists." - planForbidden: "You need write access, or a place on the crew, to plan here."

  • src/web/pages/thread.ts (labelsSubmit, dependencySubmit) and src/http/mcp.ts (the labels and wait_for tools) import THREAD_SAYS in place of the typed-out strings. Each edit in mcp.ts touches only the string's own line, plus one import line, to stay clear of other MCP work going on at the same time.
  • grep -rn under src/ for each sentence now finds only the module.

Test

  • New test/thread-words.test.ts triggers each refusal through both doors:

- Page: it posts the form and reads the message out of the flash cookie. - MCP: it calls the tool and reads the error text. - It asserts each answer is exactly THREAD_SAYS.* (MCP answers carry their error-code prefix, e.g. forbidden: …). If either door rewords a sentence, the test fails.

  • No existing test was changed. test/mcp-labels.test.ts and test/mcp-wait-for.test.ts still check the literal sentences, so they also confirm the wording is byte-for-byte the same.

Not done

  • The other plan refusals in dependencySubmit's word table (self, cycle, cross_repo, not_an_issue) and the MCP dependencyWords table stay where they are; the issue names only these three sentences.

Checks run bun x tsc --noEmit is clean. bun test passed on the new test plus the 45 other test files that touch labels, dependencies, MCP or the thread pages: 604 pass, 0 fail.

🤖 Generated with Claude Code

Closes #5564

commented

The page and MCP now read the label and plan refusals from one module, so their wording can't drift apart. What they send back is unchanged.

What moved where

  • New src/actions/thread_words.ts exports THREAD_SAYS, a const object holding the three sentences:

- labelsForbidden: "You need write access to change labels." - unknownLabel: "One of those labels no longer exists." - planForbidden: "You need write access, or a place on the crew, to plan here."

  • src/web/pages/thread.ts (labelsSubmit, dependencySubmit) and src/http/mcp.ts (the labels and wait_for tools) import THREAD_SAYS in place of the typed-out strings. Each edit in mcp.ts touches only the string's own line, plus one import line, to stay clear of other MCP work going on at the same time.
  • grep -rn under src/ for each sentence now finds only the module.

Test

  • New test/thread-words.test.ts triggers each refusal through both doors:

- Page: it posts the form and reads the message out of the flash cookie. - MCP: it calls the tool and reads the error text. - It asserts each answer is exactly THREAD_SAYS.* (MCP answers carry their error-code prefix, e.g. forbidden: …). If either door rewords a sentence, the test fails.

  • No existing test was changed. test/mcp-labels.test.ts and test/mcp-wait-for.test.ts still check the literal sentences, so they also confirm the wording is byte-for-byte the same.

Not done

  • The other plan refusals in dependencySubmit's word table (self, cycle, cross_repo, not_an_issue) and the MCP dependencyWords table stay where they are; the issue names only these three sentences.

Checks run bun x tsc --noEmit is clean. bun test passed on the new test plus the 45 other test files that touch labels, dependencies, MCP or the thread pages: 604 pass, 0 fail.

🤖 Generated with Claude Code

Approved

Clean. This only moves three refusal sentences into src/actions/thread_words.ts, and labelsSubmit, dependencySubmit and the MCP labels / wait_for tools now read them from there. No authorization, input or output path changes. The same originError / applied.error checks still decide who is refused, and only the sentence now comes from the module. The flash message is still rendered through the existing escaped flash path. At this head, a grep under src/ finds each sentence only in the module.

Tests: test/thread-words.test.ts triggers each refusal through both the page (flash cookie) and MCP, and asserts it against THREAD_SAYS. If either door rewords a sentence, the test fails. I ran it at the pull head with test/mcp-labels.test.ts and test/mcp-wait-for.test.ts: 10 pass, 0 fail. The two existing tests still check the literal sentences, so the wording is confirmed byte-for-byte the same.

The terminal (src/tui) never printed these sentences, so it has nothing to keep in step. Nothing under src/web changed apart from string sources, so the design system is unaffected. The preview answers / with 200.

One small point, not blocking: src/actions/plan.ts:209 still says its own lower-case "you need write access to plan here". If that reaches a person, a later pull could point it at THREAD_SAYS.planForbidden too.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs