Thanks Kyle,
First, I apologise for the poorly chosen subject. I indeed looks like something in my setup - I am also unable to reproduce with emacs -Q.
Since I posted this, I looked further and found that the place where the problem occurs for me is in the function `org-fix-position-after-promote'. I will paste it here for your convenience:
```
(defun org-fix-position-after-promote ()
"Make sure that after pro/demotion cursor position is right."
(let ((pos (point)))
(when (save-excursion
(beginning-of-line 1)
(looking-at org-todo-line-regexp)
(or (equal pos (match-end 1)) (equal pos (match-end 2))))
(cond ((eobp) (insert " "))
((eolp) (insert " "))
((equal (char-after) ?\ ) (forward-char 1))))))
```
Specifically, (equal pos (match-end 2)) evals to t here and to nil on a clean system. I examined the difference between the the two setups.
In my setup, the variable `org-todo-line-regexp' is set to:
"^\\(\\*+\\)\\(?: +\\(\\(?:C\\(?:ANCELLED\\|OMPLETE\\|REDENTIAL\\)\\|DONE\\|EVENT\\|FUTURE\\|HABIT\\|N\\(?:OTE\\|UMBER\\)\\|OBJECT\\|PROJECT\\|STARTED\\|T\\(?:EMP\\|ODO\\)\\|WAITING\\)?\\)\\)?\\(?: +\\(.*?\\)\\)?[ ]*$"
(yes, I know - I have too many labels...)
In the clean system, the variable is set as follows:
"^\\(\\*+\\)\\(?: +\\(TODO\\|DONE\\)\\)?\\(?: +\\(.*?\\)\\)?[ ]*$"
Incidentally, the *only* difference between the two is in group 2 which matches in my setup and does not match in the default setup. Just in case it isn't obvious, this variable appears to get autogenerated from my settings.
If I'm not mistaken, it looks like the group in my case matches the empty string and then the function decides to add a space since we are at the end of the line. The question then is, why does the empty get matched?
Well, I know why; my `org-todo-keywords' is setup as follows:
(setq org-todo-keywords
'((type "TODO(t!)" "|" "DONE(d!)" "CANCELLED(c@)")
(type "HABIT(h!)" "|" "DONE(d!)")
(type "WAITING(w@/!)" "FUTURE(f!/@)" "STARTED(s@/!)" "|" "DONE(d!)")
(type "|" "NOTE(n)" "NUMBER(#)" "CREDENTIAL($)" "OBJECT(o)" "TEMP(e)")
(type "EVENT(n)")
(type "PROJECT(p!)" "|" "COMPLETE(m@)")
(type "(-)")
))
Disregard that there are key collisions, and notice the last entry, which I made to be able to easily remove the TODO tags altogether. Well, I guess I'll have to lose it.
Thanks for the attention!
Kosta