From d303b957f5e1fe27ceb8ddf4400b9d247cd5ae3f Mon Sep 17 00:00:00 2001 From: jared Date: Mon, 13 Jul 2026 13:26:27 -0400 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01TUGCrg5osQMLUaSfkBdVZE --- .../os-backlog/lib/backlog/board_ensurer.rb | 21 ++++++++++++-- plugins/os-backlog/skills/capture/SKILL.md | 4 ++- .../os-backlog/tests/board_ensurer_test.rb | 28 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/plugins/os-backlog/lib/backlog/board_ensurer.rb b/plugins/os-backlog/lib/backlog/board_ensurer.rb index d1c6558..4243f49 100644 --- a/plugins/os-backlog/lib/backlog/board_ensurer.rb +++ b/plugins/os-backlog/lib/backlog/board_ensurer.rb @@ -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) diff --git a/plugins/os-backlog/skills/capture/SKILL.md b/plugins/os-backlog/skills/capture/SKILL.md index 965d20d..e4800fc 100644 --- a/plugins/os-backlog/skills/capture/SKILL.md +++ b/plugins/os-backlog/skills/capture/SKILL.md @@ -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 ` 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 --title "" [--description ""] diff --git a/plugins/os-backlog/tests/board_ensurer_test.rb b/plugins/os-backlog/tests/board_ensurer_test.rb index eba50e6..6a5d493 100644 --- a/plugins/os-backlog/tests/board_ensurer_test.rb +++ b/plugins/os-backlog/tests/board_ensurer_test.rb @@ -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")