hub: an owner gets forbidden creating a join in their own project (wolfgangs-HA, 2026-11-06 06:15Z)
tsk-7jhpzrvsnzm3 in progress Maity Coder Claude 003 (maity-coder-claude-003)
Report (via maity-marketing-head, from the project owner): creating a join ("new agent") in the owner's own project fails in the web UI with "Kein Beitritt erstellt: Dafür fehlen dir die Rechte." That is join.failed + error.forbidden, i.e. POST /projects/{owner}/{slug}/joins answered 403 forbidden. The owner says the same happened before.
On main, create_join (hub/src/maity_hub/routes/joins.py) answers forbidden from these places; details.reason tells them apart:
_require_member_rule→forbidden("members:admin"): the caller's membership lacksmembers:admin(an owner should have it)._require_owner_for→ reasonowner_only: trust admin / role proxy asked by a non-owner._agent_for_join→_limit("agent"), reasonmember_join_limit: the join names an existing agent that is not the caller's own._check_member_limits/_require_slot→member_join_limit/member_agents_max(member rule only).
Steps:
- Find the reason (hub log for that request, or reproduce with the same body on a test project).
- Reproduce with a test that is red on main, then fix; if the web form sends something the hub refuses (e.g. a picked agent of another human), the web should show the named reason instead of the bare "no rights".
- Name what "again" was the first time, if the history shows it.
Cause not reproduced yet.
Notes
Lotse (lotse) ·
Ops read the access log (no reason field logged): GET joins 200 at 06:13:18Z; 28 POST joins 403 between 06:13:36 and 06:13:45Z (about 3 per second, looks like a client retrying the same refused request); one more 403 at 06:17:08Z; then POST 201 at 06:17:32Z, so a join was created in the end. The 403 access lines carry no details.reason, so the cause is still open. Two findings for whoever takes this: (1) the reason of a 403 is not logged, which makes this class of report hard to trace; (2) something sent the refused POST 28 times in 9 s.
Lotse (lotse) ·
The owner's own account (06:23Z): the join at 06:17:32Z is his, made from the Members page, which asked for the second factor. The "Join an agent" form did not ask, and showed only "Dafür fehlen dir die Rechte". His request: check every place in the web UI where an agent can be joined and make the second-factor question work the same everywhere; and when the factor is missing, say so instead of "no rights".
What main says (not yet reproduced):
- #1399 (c9e77979, in the live deploy 4846503) already wraps the join POST in web/js/join.js:400 in M.stepUp.run.
- web/js/step-up.js asks only for reasons in its ASK table: reauthentication_required, step_up_required (+ step_up_mail). Any other forbidden reason is rethrown, and join.js then shows the bare error.forbidden.
- The hub also refuses with reason second_factor_required (hub/src/maity_hub/auth/second_factor.py:213, "this session owes the second factor"; also :739/:741). That reason is not in ASK, so it would surface exactly as "no rights".
- Other candidate: a browser still running a pre-#1399 join.js from cache.
- 28 refused POSTs in 9 s at 06:13:36-45Z: worth checking whether step-up.js's retry loop or join.js resubmits without a pause.
Scope: reproduce which reason the form got (test red on main), handle it in step-up.js or the form, name the second factor in the message, and list every UI path that creates a join (join.js, members page, any other) with a test that each asks for the factor.