os-backlog: board-ensure fails loudly when PLANKA_HUMAN_USER_ID is unset (card #1818408943538406701)

fix_visibility now raises MissingHumanUserIdError instead of silently
creating a bot-only Planka project when no human user id is available.
Existing projects still ensure without the env var. Supersedes card
#1818396164853073182.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TUGCrg5osQMLUaSfkBdVZE
This commit is contained in:
jared 2026-07-13 13:26:27 -04:00
parent b0a21d6211
commit d303b957f5
3 changed files with 50 additions and 3 deletions

View File

@ -30,6 +30,9 @@ module Backlog
class BoardEnsurer
Result = Struct.new(:board, :created, :project, :lists_created, :labels_created, keyword_init: true)
# Raised instead of silently creating a bot-only-visible Planka project.
class MissingHumanUserIdError < StandardError; end
# @param client [Planka::Client] an already-authenticated client
# @param human_user_id [String, Integer, nil] Planka user id of the
# human who must be able to see bot-created projects. Falls back to
@ -94,9 +97,23 @@ module Backlog
end
# Quirk 2: null the bot's sole ownerProjectManagerId and add the human
# as a project manager, so the project shows up for them.
# as a project manager, so the project shows up for them. Only reached
# right after find_or_create_project creates a brand-new project — an
# already-existing project never calls this and so never requires
# human_user_id.
def fix_visibility(project)
return unless @human_user_id
unless @human_user_id
# The Planka API has no atomic create-with-manager call, so the
# project already exists (bot-only-visible) by the time we can
# detect the missing id — we deliberately don't try to roll it
# back/delete it here; raise loudly instead and let a human fix
# visibility (env var + rerun, or the Planka UI) after the fact.
raise MissingHumanUserIdError,
"cannot make new Planka project #{project.name.inspect} visible to a human: " \
"no human_user_id given and PLANKA_HUMAN_USER_ID is unset — the project was " \
"created bot-only; set the env var and fix visibility manually or via the " \
"Planka UI"
end
@client.projects.add_manager(project.id, @human_user_id)
@client.projects.update(project.id, ownerProjectManagerId: nil)

View File

@ -37,7 +37,9 @@ All commands use the plugin CLI at `${CLAUDE_PLUGIN_ROOT}/bin/os-backlog`.
`~/dev/`, `Clients` under `~/clients/`, `Servers` under `~/servers/`. Pass `--project <name>`
to target (or create) any other project explicitly — board-ensure creates the project too if
it doesn't exist yet. There's no command to move an existing board between projects; that's a
manual step in the Planka UI.
manual step in the Planka UI. Creating a NEW Planka project requires `PLANKA_HUMAN_USER_ID`
(or an explicit human user id) — board-ensure fails loudly rather than silently creating a
bot-only project.
4. **Create the card at Backlog:**
```bash
${CLAUDE_PLUGIN_ROOT}/bin/os-backlog card-add --board <board> --title "<short imperative title>" [--description "<context: repo, file paths, why>"]

View File

@ -132,6 +132,34 @@ class BoardEnsurerTest < Minitest::Test
assert_equal "Servers", result.project.name
end
def test_raises_missing_human_user_id_error_when_creating_new_project_without_one
original = ENV.delete("PLANKA_HUMAN_USER_ID")
client = FakePlankaClient.new
ensurer = Backlog::BoardEnsurer.new(client: client)
error = assert_raises(Backlog::BoardEnsurer::MissingHumanUserIdError) do
ensurer.ensure("llf-schema", project_name: "Dev")
end
assert_includes error.message, "PLANKA_HUMAN_USER_ID"
ensure
ENV["PLANKA_HUMAN_USER_ID"] = original
end
def test_ensure_against_existing_project_does_not_raise_without_human_user_id
original = ENV.delete("PLANKA_HUMAN_USER_ID")
client = FakePlankaClient.new
seeding_ensurer = Backlog::BoardEnsurer.new(client: client, human_user_id: "human-1")
seeding_ensurer.ensure("board-a", project_name: "Dev")
ensurer = Backlog::BoardEnsurer.new(client: client)
result = ensurer.ensure("board-b", project_name: "Dev")
refute result.project.nil?
ensure
ENV["PLANKA_HUMAN_USER_ID"] = original
end
def test_ensure_rejects_archived_prefixed_name
client = FakePlankaClient.new
ensurer = Backlog::BoardEnsurer.new(client: client, human_user_id: "human-1")