Website repository for https://ipcdocs.lint.ai/
This website hosts the IPC documentation for Lint.ai. If you are not developing modules for a Lint.ai implementation, it is unlikely to be of service to you; please visit Lint.ai for more information on the service or how to get started.
The IPC protocol utilized by Lint.ai is exceedingly prescriptive, because it represents the boundary layer between code you can trust (your Lint.ai modules, Lint.ai itself) and code that could be coming from anyone allowed to make pull requests in your repository. Full details of the protocol are contained here, but briefly, conversations over the IPC socket look like this, repeated as many times as necessary:
Client: <verb> <arguments...>\n
Server: [OK|NO|ERR] <additional information>\n
To speak to the server, open a UNIX socket to /mnt/ipc/lgtm.ipc. This socket
is created before the first module runs, and cannot be removed or manipulated
in the execution context. Examples of how to open such a socket are available
in the reference implemenation under the
lib/ directory.
GETPUBLIC <short_GH_path> <dest>GETPRIVATE <key_id>|default <short_GH_path> <dest>These two work similarly. <short_GH_path> is easiest to explain by example;
given a Github cloning path like https://github.com/lint-ai/lgtmlib.git or
git@github.com:lint-ai/lgtmlib.git, the short path is lint-ai/lgtmlib.git.
<dest> is a name consisting only of ASCII characters in the ranges
[a-zA-Z0-9.-] which does not start with a . or a -. The requested
resource will be made available to the container at that location within the
runtime_path.
When the checkout is completed, the response is OK, optionally followed by a
space (\x20) and debugging data. If the request is invalid or malformatted,
the response is NO, optionally followed by a space and debugging data. If an
error occurs, the response is ERR, optionally followed by a space and
debugging data.
OK clean checkout of lint-ai/lgtmlib.git is now at lgtmlib\n
NO too few arguments to GETPRIVATE\n
ERR Github says 404 Not Found\n
pull_request events)The behavior of these verbs in a non-PR context is undefined. Always check
your context via /L/is or
similar.
STAMPSTAMP causes the current PR or branch to be stamped as (to the best of the
user’s ability) “approved” for merge. It only takes effect if the complete
chain of modules finish successfully (i.e., clean shutdown with retval==0).
STAMP always replies with OK I will try\n and does not (and can not, due to
its design) guarantee that a stamp will actually occur.
UNSTAMPIf there is a STAMP pending, UNSTAMP stops it. If there is no stamp
pending, it’s a no-op. The reply is always OK <debugging info>\n.
STAMPEDIf there is a STAMP that has not been UNSTAMPED, and there is no pending
REJECT, the reply will be OK STAMP\n or OK STAMP <debugging info>\n. In
any other case, the word STAMP in the reply will be replaced with NOSTAMP.
REJECT [<message>]Reject (request changes) on the PR. Unlike STAMP, once acknowledged, this
SHOULD be honored even if other parts of the .lgtm fail. This is not
foolproof, however; the nature of the transaction is such that intervening
repo events—or various catastrophic internal failures in the LGTM
runtime—may preclude us from explicitly rejecting the PR. However,
an acknowledged REJECT does guarantee that if a PR review is left as a
result of this particular LGTM run, it will request changes. The reply reflects
this uncertainty, and is thus always OK I will try\n.
Sending a REJECT will mask (but does not remove) any STAMP; a subsequent
UNREJECT will therefore have the practical effect of reinstating the old
STAMPED-ness. If this is undesired, take care to both REJECT and
UNSTAMP; the latter is a no-op in the worst case.
The <message> must conform to the permitted byte sequences, and is sent to
Github as UTF8. Consider using REJECT64 in order to have complete message
control. The message is optional, but users are cautioned that there may
be social consequences to programmatically rejecting a PR without at least
making an attempt to explain why you’ve done so.
Multiple REJECTs are accepted. A REJECT on an existing REJECT will cause
the messages to be concatenated (in the order received) in the review comment
accompanying the “request changes” review, each separated by two newlines
(\n\n). If this behavior is not desired, UNREJECT first. Different methods
of concatenation or merging of output—which is to say, coordinating
amongst modules—is outside of the scope of the protocol.
REJECT64 [<b64 message>]Exactly as REJECT, but the message is base64-encoded.
UNREJECTAs with UNSTAMP, so with UNREJECT. Note the caveat with regards to
reverting to previous STAMPED-ness mentioned under REJECT.
Assuming success, the reply is OK PR purification proclaimed.\n.
REJECTEDAs with STAMPED, but reflecting purely REJECT/UNREJECT status instead. A
reply will start with either OK REJECT or OK NOREJECT.
push events)The behavior of these commands outside of a COMMIT (Github calls them push)
event is undefined. See /L/is
for a way to check your current context.
COMMIT <message>Request a commit with the <message>. Takes effect based on the state of the
repository checkout at the end of the run, and, as with STAMP, only if all
other modules exit cleanly. Any intermediate commits are meaningless, and
should generally be avoided; the commit sent to Github will always be exactly
one ref deep and will contain all changes made relative to the original
checkout.
A <message> must be provided. Since the protocol forbids embedded \n and
many other useful characters (like :), most users will opt for COMMIT64.
Indeed, this is what
/L/commit uses under the
hood.
If there is already a COMMIT, messages will be concatenated in the order
received, separated by \n\n. To avoid this behavior, UNCOMMIT and then
COMMIT.
If there are no changes after all modules have run, no commit will happen (and
the message will be discarded). Thus, a message like
chore(lint.ai): Automated fixups is perfectly valid (use of unpermitted
characters notwithstanding), and will only appear in the commit log if any
actual changes were made.
Reply is always OK I WILL TRY\n (barring errors, of course).
COMMIT64 <b64 message>Exactly as COMMIT, but the message is base64-encoded.
UNCOMMITIf there is a commit pending, cancels it (and clears the message buffer). If
there is no commit pending, it’s a no-op. In all cases, the reply is exactly
OK apeirophobia acknowledged\n (barring errors).
COMMITTEDAs with STAMPED, reply will start with either OK COMMIT or OK NOCOMMIT.
VERSION [client=<str> ver=<semver> uuvid=<uniq>] […]Reply will be OK <protosemver> <server> <ssemver> <caps>\n, where
<protosemver> is a SEMVER of the maximum version of
this protocol supported by the server, <server> is a string that uniquely
identifies the provider of the server (e.g. lint.ai), <ssemver> is a SEMVER
of the server itself, and <caps> is reserved for future use.
Clients are encouraged, but not required, to utilize this command first, and to identify themselves in a manner corresponding to:
VERSION client=lgtmlib ver=1.0.0 uuvid=608a6dca816d9527bb81a042cc0f3ce31673de2c
uuvid is a subrelease-specific universally-unique identification of the client
(a simple implementation of which would be the git SHA of the last commit to the
repository, as shown here). Servers MAY reject requests without this
identifying information by returning NO with human-readable information to
explain the rejection intended to be shared with the operator. If a server does
so, it MUST immediately close the client’s connection after sending this reply.
In future versions of this specification, arguments to and responses from VERSION are anticipated to be used in negotiating advanced protocol capabilities between clients and servers. Both client and server implementations are therefore encouraged to be conservative in their assumptions.
The request language consists of verbs and arguments, terminated by newline
(\n). Both verbs and arguments MUST NOT contain bytes outside the ASCII
range [a-zA-Z0-9.=/+-], and MUST BE separated from one another and from the
verb by exactly one space. Unimplemented verbs result in a NO response
(but see below for an important caveat about using this
behavior for protocol negotiation).
Requests are not guaranteed to be processed if their length exceeds 2,047 bytes.
| Response status | Meaning ! |
|---|---|
| OK | The requested operation has been performed. |
| NO | No work was done. Do not re-send this request. |
| ERR | An error occurred while handling this request. Retries MAY be possible. |
All requests, defined as a sequence of bytes terminated by a newline, regardless
of syntactical or semantic integrity, MUST engender a response consisting of a
single line of arbitrary non-null, non-\n bytes ending in \n, the first word
of which is a status (OK, NO, or ERR) and the remainder of which is either
specified by the verb definition above or is implementation-specific. Note that
responses ARE NOT constrained by the character-set limits imposed on requests.
Clients MUST NOT rely on gradations between different information following NO
or ERR responses to guide their behavior or to make inferences about internal
server state. These semantics may change at any time and for reasons opaque to
the client itself (e.g. because a different backend server has been hotswapped
in). Servers MAY use ERR or NO without (or even with misleading) additional
information to maintain a desired security posture. The client retry behavior
distinction between ERR and NO SHOULD, however, always be honored if
possible.
A client MUST NOT retry an operation resulting in a NO response without
modification to the request itself (or a new session).
A client MAY retry a request beginning with ERR as many times as the
server will allow.
Any request (especially blank requests) MAY be processed by the server as a
NOOP. Requests processed as a NOOP must receive a reply beginning with ERR
or NO, depending on desired client retry behavior, that contains the keyword
NOOP as the next token (ex.: NO NOOP, NO NOOP bad encoding, or ERR NOOP
backend restarted please retry). Servers MAY NOT use NOOP if any part of the
command may have been processed (i.e., a verb-handler called), to allow the
client to distinguish between an ERR resulting from a verb failing to
complete successfully (whereupon the client might be afraid to rely on state
unless the verb is idempotent), versus a request which never invoked a verb
handler at all.
A server MAY elect not to process a request, for example because it is too busy,
because the request is blank, because the request exceeds the size of its
internal buffers, or for any other implementation-specific reason. If this
decision is made, the request is considered a NOOP, but the server MUST read all
data until a \n is reached before sending an appropriate NOOP reply. In
response to any other behavior (in particular, closing the session), a client
MAY interpret the issue as a temporary error and retry accordingly.
While provision is made for enforcing limited buffer sizes in the server, responses are not limited in length in any way. Clients SHOULD take appropriate precautions if their request might generate a very large response body. Likewise, if a client’s connection to the server, or if the server itself, isn’t trusted or reliable, clients SHOULD be prepared to handle bizarre or malicious responses.
While a single connection can handle any number of VERBs and responses, a server MUST accept repeated dis- and re- connection, even in circumstances where connection-pooling would have been possible. Some modules—especially those relying on lgtmlib’s support for shell scripts—MAY open and close the socket repeatedly in the course of a single execution. All of this is normal and permissible. That said, the connection queue is only one-deep; parallelizing modules is possible, but IPC is not parallelizable at this time.
New capabilities MAY NOT be negotiated via any means other than the VERSION
request and response. In particular, techniques such as reading the
additional information that follows a verb other than VERSION, or using
a verb without arguments or with intentionally-nonoperational arguments (such
as --help) is considered a violation of the protocol, because it decreases
implementation flexibility and increases the server operators’ monitoring
burden when looking for misbehaving clients.
Client implementors are reminded that requests are limited to 2,047 bytes, and
server implementors that there is no limit on the number of VERSION requests
sent. Given that this enables an arbitrary duration of negotiations, creators
of clients and servers MAY enforce limits, develop shorthands, extend buffers,
or other techniques to speed processing. Any such changes are considered an
implementation detail and left to each individual implementor’s discretion.