Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

395 commit(s) on main

update readme Keenan Nemetz · 24m ago 32290114
source preview history blame raw 7.6 KiB
1# Issues
2
3Your issues belong to the repository, not to a vendor. They clone with the code,
4keep working when a hosting service has an outage, and stay readable in any text
5editor.
6
7An issue is a Markdown file stored on a special branch called `vole`. Every clone
8of the repository carries the issues and their history, so there is no separate
9issue database to back up.
10
11## Where issues live
12
13```text
14issues/<ulid>/issue.md # the issue
15issues/<ulid>/comments/<ulid>.md # a comment
16```
17
18Here is a complete issue:
19
20```markdown
21---
22title: "Nothing renders on the docs page"
23state: open
24author: you
25created: 2026-10-22T21:30:00Z
26labels:
27 - bug
28---
29Steps to reproduce...
30```
31
32The top section, between the `---` lines, is YAML metadata. The rest is the
33Markdown body. Titles and other free text are always quoted, so characters like
34`#` or a leading `-` cannot change the meaning of the file.
35
36An issue id is a 26-character ULID. It uses uppercase letters and digits, but not
37`I`, `L`, `O`, or `U`. A UUID will not work.
38
39## Numbers
40
41Nobody types 26 characters, so every issue and change request also has a small
42number: `#1`, `#42`. Issues and change requests share one sequence, oldest first,
43the way one forge counts both in a single run.
44
45The number is **derived, not stored**. It is the position of the commit that
46created the entity in the metadata branch's history. Nothing is added to the file,
47no counter is kept, and there is nothing to migrate: an existing repository gets
48its numbers the first time anyone asks. Two clones of the same history produce the
49same numbers, and `vole admin index rebuild` reproduces them from git.
50
51The ULID is still the identity — it is the path, the front matter's neighbour in
52`closes:`, the thing in the commit messages — so a number is a convenience, not a
53reference you should quote in a script. The one way to make one wrong is to rewrite
54the branch history: an admin redaction force-push renumbers everything after the
55commit it rewrote.
56
57Every command that takes an id takes a number instead, with or without the `#`:
58
59```sh
60vole issue show 42
61vole issue close '#42'
62vole change show 1
63```
64
65## Writing an issue without the CLI
66
67```sh
68git switch --orphan vole
69ID=01M5JR2JK29KKMAA1T9MDNTPBP
70mkdir -p issues/$ID
71cat > issues/$ID/issue.md <<'MD'
72---
73title: "Nothing renders on the docs page"
74state: open
75author: you
76created: 2026-10-22T21:30:00Z
77labels:
78 - bug
79---
80Steps to reproduce...
81MD
82git add -A
83git commit -m "issue: docs page blank"
84git push origin vole
85```
86
87Vole checks the file when you push. The `author` field must be the account you push
88as unless you hold `maintain`, which is the one place the browser is stricter than a
89push: a contributor writes as itself. If someone else pushed first, the CLI puts your
90change on top of theirs and tries again.
91
92## From the browser
93
94When browser sign-in is enabled (see [Accounts and access](accounts-and-access.md#browser-sign-in)),
95a signed-in contributor or maintainer can open an issue or comment from the
96repository's issue page. The author is the account whose passkey opened the
97session; the form cannot choose a different name. The change is written with the
98same metadata writer the CLI uses, so it is the same file on the same branch. A
99contributor can use this form or the CLI; pre-receive asks the same capabilities the
100form does, so neither offers more than the other.
101
102The issue's author or a maintainer can correct its title with the pencil beside
103the heading, or its description from the card's menu. Each opens its own field in
104place, so saving does not leave the page or replace the discussion. The discussion,
105labels, and assignees are left alone, and the edit is an ordinary metadata commit
106like the one the CLI writes.
107
108## Using the CLI
109
110```sh
111vole issue new --title "Nothing renders on the docs page" -m "Steps..." -l bug -a wilbur
112vole issue comment -m "Also affects search" 01M5JR2JK29KKMAA1T9MDNTPBP
113vole issue edit-comment 01M5JR2JK29KKMAA1T9MDNTPBP 01M5N1PRC4GJPZNEEG8PR8J4D6 -m "Corrected."
114vole issue rm-comment 01M5JR2JK29KKMAA1T9MDNTPBP 01M5N1PRC4GJPZNEEG8PR8J4D6
115vole issue edit 01M5JR2JK29KKMAA1T9MDNTPBP --title "A better title" -l bug -l docs
116vole issue assign 01M5JR2JK29KKMAA1T9MDNTPBP wilbur
117vole issue unassign 01M5JR2JK29KKMAA1T9MDNTPBP wilbur
118vole issue close 01M5JR2JK29KKMAA1T9MDNTPBP
119vole issue reopen 01M5JR2JK29KKMAA1T9MDNTPBP
120vole issue list --state open -l bug --assignee wilbur --format '%(number) %(title)'
121vole issue show 01M5JR2JK29KKMAA1T9MDNTPBP # a prefix is enough
122vole issue show 42 # the number is enough
123```
124
125`--format` understands `%(number)`, `%(id)`, `%(shortid)`, `%(state)`, `%(author)`,
126`%(title)`, `%(labels)`, `%(assignees)`, and `%(created)`. `%(shortid)` is a
127prefix of the ULID, which is mostly timestamp; `%(number)` is the alias people
128read and quote.
129
130The CLI commits and pushes for you. It uses the account your SSH key belongs to,
131not the name in `git config`. If you want to commit without pushing, add
132`--no-push`.
133
134You can also create an issue in a new directory with no remote configured:
135
136```sh
137git init bug-report
138cd bug-report
139vole issue new --title "the docs page is blank" \
140 --remote ssh://git@forge.example.com/demo.git
141```
142
143## Rules
144
145- Issues and comments live under `issues/` and `changes/` on the `vole` branch.
146 Other paths are rejected. Releases have their own paths, written by Vole. CI
147 records are not stored on the branch; they live in `data/runs.db`.
148- History on the `vole` branch must be linear. Merge commits are not allowed.
149- Force-pushing `vole` requires repository admin and is recorded in
150 `data/audit.log`. It is meant for redaction, not normal work.
151- Writing `#42` in a body links to that entity. Because one sequence holds both
152 kinds, the link goes through `/n/42`, and the server resolves it to the issue or
153 the change request that holds the number. `#<ulid>` links by id and is the form
154 that keeps working if history is ever rewritten. Commits that mention an id are
155 shown as backlinks on the issue page.
156- Writing `@name` links to that account's profile. It is the same syntax a
157 workflow's `mentioned` filter matches, so the page and a job agree about who was
158 addressed. Code samples are skipped, and a name that is not an account stays
159 text.
160- An assignee must be a valid account name: lowercase letters, digits, `-`, and
161 `_`. The name is stored as written because account names are never reused. A
162 value that is not an account name cannot be an assignee.
163- The web page can filter by state, label, and assignee, and search titles and
164 bodies.
165
166## Comments
167
168A comment is a file at `issues/<id>/comments/<ulid>.md` with `author`,
169`created`, and optionally `reply_to` metadata. `reply_to` names any comment in
170the same thread. The web renders one level of nesting by collecting a root's
171descendants beneath it.
172
173Only the author of a comment can edit or delete it. Vole compares the author on
174the old version of the file with the account behind the SSH key. An admin can
175override this, for example to repair a thread after someone leaves, and the
176override is recorded in `data/audit.log`.
177
178Deleting a comment replaces it with a tombstone ("Comment deleted") so replies
179keep the thread they answer. The text remains in Git history, and a deleted
180comment cannot be edited. Editing a comment does not add an "edited" field; the
181commit carries the time. Deleting is not redaction. Redaction means an admin
182force-push.
183
184The web comment menu offers Reply, Copy, Edit and Delete. Editing and deleting
185are limited to the author or an admin, and deletion leaves a tombstone so replies
186keep their thread.
187
188The issue itself works differently. Closing, relabeling, or assigning someone
189else's issue is normal triage, not a claim of authorship.
190