Skip to content

feat: add docker-compose quickstart#23

Closed
khaliqgant wants to merge 5 commits intomainfrom
feat/docker-quickstart
Closed

feat: add docker-compose quickstart#23
khaliqgant wants to merge 5 commits intomainfrom
feat/docker-quickstart

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Mar 29, 2026

git clone → cd docker → docker compose up → working relayfile + relayauth stack with seed data.


Open with Devin

khaliqgant and others added 5 commits March 29, 2026 12:08
Five runnable TypeScript examples covering the core relayfile SDK:
- 01: read files (listTree, readFile, queryFiles)
- 02: write files (writeFile, bulkWrite, optimistic locking)
- 03: webhook ingestion (ingestWebhook, computeCanonicalPath)
- 04: realtime events (getEvents polling with cursors)
- 05: scoped agent permissions (path-restricted tokens, 403 handling)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +125 to +138
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(resolve, ms);

if (!signal) {
return;
}

const onAbort = () => {
clearTimeout(timer);
reject(signal.reason ?? new DOMException("The operation was aborted", "AbortError"));
};

signal.addEventListener("abort", onAbort, { once: true });
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 sleep() leaks abort listener on every normal timer resolution

When the setTimeout fires normally and calls resolve, the onAbort listener is never removed from signal. The { once: true } option only auto-removes the listener when the abort event fires, not when the timer resolves. Since runLoop calls sleep on every poll iteration, a new orphaned listener accumulates on the AbortSignal each cycle. Over the lifetime of a long-running WritebackConsumer, this causes unbounded listener growth on the signal.

Comparison with client.ts sleep which properly cleans up

The existing client.ts:807-826 sleep correctly removes the listener on both paths:

const timer = setTimeout(() => {
  signal?.removeEventListener("abort", onAbort); // ← cleanup on normal resolve
  resolve();
}, delayMs);

The new writeback-consumer.ts:125 passes resolve directly to setTimeout, never getting the chance to remove the listener.

Suggested change
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(resolve, ms);
if (!signal) {
return;
}
const onAbort = () => {
clearTimeout(timer);
reject(signal.reason ?? new DOMException("The operation was aborted", "AbortError"));
};
signal.addEventListener("abort", onAbort, { once: true });
});
await new Promise<void>((resolve, reject) => {
const onDone = () => {
signal?.removeEventListener("abort", onAbort);
resolve();
};
const timer = setTimeout(onDone, ms);
if (!signal) {
return;
}
const onAbort = () => {
clearTimeout(timer);
reject(signal.reason ?? new DOMException("The operation was aborted", "AbortError"));
};
signal.addEventListener("abort", onAbort, { once: true });
});
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +13 to +17
curl -fsS -X PUT "$RELAYFILE/v1/workspaces/$WS/fs/file?path=$file_path" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Correlation-Id: $correlation_id" \
-H "Content-Type: application/octet-stream" \
-d "$content" >/dev/null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 seed.sh sends raw content instead of required JSON body and omits mandatory If-Match header

The rf_put function in seed.sh sends raw file content with Content-Type: application/octet-stream and no If-Match header. However, the Go server's handleWriteFile (internal/httpapi/server.go:1449) requires both: an If-Match header (returns 412 without it, line 1458) and a JSON body parsed via decodeJSONBody (returns 400 for non-JSON, line 1482). Since the script uses set -e and curl -fsS, the first rf_put call will exit non-zero, aborting the entire seed process. The Docker quickstart will report "Ready" without any files seeded, or more likely the seed container will crash.

Expected curl format matching the API contract

The SDK's writeFile sends If-Match: * and a JSON body {"contentType": ..., "content": ...}. The seed script should do the same:

curl -fsS -X PUT "$RELAYFILE/v1/workspaces/$WS/fs/file?path=$file_path" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Correlation-Id: $correlation_id" \
  -H "If-Match: *" \
  -H "Content-Type: application/json" \
  -d "{\"contentType\":\"text/plain\",\"content\":\"$content\"}" >/dev/null
Prompt for agents
In docker/seed.sh, the rf_put function (lines 8-18) needs two fixes:

1. Add an If-Match header with value * (for create-or-overwrite) to the curl command.
2. Change the Content-Type from application/octet-stream to application/json, and wrap the content in a JSON body matching the server's expected format: {"contentType": "<mime>", "content": "<content>"}.

Note that the content being passed includes multi-line strings and JSON strings with special characters, so the JSON body construction must properly escape those. Consider using a tool like jq to safely build the JSON body, or carefully escape the content.

The rf_put function signature may also need an additional parameter for the content type (e.g. text/markdown vs application/json) so each call site can specify the correct MIME type.

Affected call sites are on lines 28-30 (welcome.md, text/markdown), line 32 (metadata.json, application/json), and line 33 (agents.json, application/json).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@khaliqgant
Copy link
Copy Markdown
Member Author

Superseded by #25 — WritebackConsumer already merged via #24, docker + examples in #25

@khaliqgant khaliqgant closed this Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant