Skip to content

feat(daemon): periodic IPFS repo GC over kubo RPC, upgrade kubo 0.43.0 + pkc-js 0.0.77 - #120

Merged
Rinse12 merged 2 commits into
masterfrom
upgrade-kubo-0.43-repo-gc-119
Aug 7, 2026
Merged

feat(daemon): periodic IPFS repo GC over kubo RPC, upgrade kubo 0.43.0 + pkc-js 0.0.77#120
Rinse12 merged 2 commits into
masterfrom
upgrade-kubo-0.43-repo-gc-119

Conversation

@Rinse12

@Rinse12 Rinse12 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Closes #119

What

  • kubo 0.42.0 -> 0.43.0, @pkcprotocol/pkc-js 0.0.73 -> 0.0.77
  • New src/ipfs/repoGc.ts: periodic IPFS repo garbage collection driven over the kubo RPC API
  • New daemon flags --enableIpfsGc / --no-enableIpfsGc (default on) and --ipfsGcIntervalMinutes (default 60)
  • Datastore.StorageMax left at kubo's 10GB default

Why not --enable-gc

The obvious implementation is kubo's own --enable-gc daemon flag, which already does hourly, watermark-gated GC. It does not work here.

A daemon started with --enable-gc never exits in response to POST /api/v0/shutdown. It logs cannot access config, repo not open and lingers as a half-shutdown zombie; SIGTERM still works. pkc-js POSTs that exact endpoint when it rewrites the kubo Routing config on first connect and relies on the daemon restarting kubo afterwards, so the flag wedges the supervision loop.

Controlled A/B on an idle machine, same deps, only the flag differing:

Build test/cli/daemon.test.ts
without --enable-gc 23/23 pass
with --enable-gc 4 failed / 19 passed

Standalone repro, no bitsocial-cli involved:

Shutdown path plain daemon --enable-gc
POST /api/v0/shutdown exits ~0.1s never exits (permanent past 300s)
SIGTERM exits exits

Root cause: maybeRunGC gives PeriodicGC the command request context, but the shutdown command only calls nd.Close(), which cancels the node context — so gcErrc never closes and daemonFunc blocks forever draining merge(...). Every other channel in that merge is node-context-wired; gcErrc is the lone exception.

Reported upstream as ipfs/kubo#11424. It reproduces on 0.24.0, 0.42.0 and 0.43.0, so it is long-standing — and 0.24.0 is the era of f228d7d, meaning this is what caused that Dec 2023 revert, not the MFS/GC wedge described in #119. The MFS wedge was real but coincidental, and is genuinely fixed in 0.43.0 (verified: 64 rounds of files write + files stat against GC every 5s at a 1MB ceiling, MFS fully intact, all 64 unpinned garbage blocks reclaimed).

How the scheduler behaves

Mirrors kubo's own policy rather than inventing a second one:

  • checks every --ipfsGcIntervalMinutes (default 60, matching Datastore.GCPeriod)
  • POST /api/v0/repo/stat?size-only=true, and only GCs once RepoSize >= 90% of StorageMax (matching Datastore.StorageGCWatermark)
  • size-only is deliberate — the default repo/stat walks the entire flatfs blockstore, which on the repos this exists for is millions of files
  • single-flighted so a long GC never has a second stacked on it, timer unref'd, and errors contained inside the tick so a rejected interval callback cannot take the daemon down
  • runs against whichever kubo the daemon talks to, including an externally started one via --pkcOptions.kuboRpcClientsOptions

This also closes a real gap: pkc-js GCs on the same watermark but only from a started local community's IPNS sync, so a daemon that is up with no community started would never reclaim anything.

Note GC only reclaims unpinned blocks. It bounds unpinned growth; it does not cap a repo whose bulk is pinned.

Testing

  • 12 new unit tests in test/kubo/repoGc.test.ts covering the watermark skip, the GC path, size-only, the no-StorageMax fallback, force, both failure paths, wildcard-address rewriting, interval/stop behaviour, single-flighting, and the unhandledRejection guard
  • Verified against a live kubo 0.43.0 node: correctly skipped below the watermark; a forced run reclaimed 10 CIDs, shrinking the repo 1,010,267 -> 9,727 bytes
  • Ipns.RecordLifetime / RepublishPeriod: the 0.43.0 startup validation is a non-issue — the CLI writes neither key, and 0.43.0 accepts the "" that existing repos carry (verified against a repo seeded with production values)
  • Full suite green: 41 files, 322 passed, 1 skipped

Summary by CodeRabbit

  • New Features

    • Added automatic IPFS repository garbage collection while the daemon runs.
    • Added options to enable or disable garbage collection and configure its interval.
    • Garbage collection runs only when storage usage reaches the configured threshold and reports reclaimed content.
  • Documentation

    • Updated daemon command usage and flag documentation with the new IPFS garbage-collection options.
  • Maintenance

    • Updated IPFS and protocol client components to newer versions.

…0 + pkc-js 0.0.77

Upgrades kubo 0.42.0 -> 0.43.0 and @pkcprotocol/pkc-js 0.0.73 -> 0.0.77, and adds a
periodic repo GC so a long-running daemon reclaims unpinned blocks. Without it one
production node reached ~190GB against a 10GB StorageMax and exhausted disk and inodes.

GC is driven over the kubo RPC API on our own schedule rather than via kubo's
--enable-gc daemon flag. A daemon started with --enable-gc never exits in response to
POST /api/v0/shutdown, lingering as a half-shutdown zombie (SIGTERM still works). pkc-js
POSTs that endpoint when it rewrites the kubo Routing config on first connect and relies
on the daemon restarting kubo, so the flag wedges the supervision loop -- it fails 4 kubo
restart tests deterministically. Reported upstream as ipfs/kubo#11424; reproduces on
0.24.0, 0.42.0 and 0.43.0, which is also why the earlier attempt at the flag (f228d7d)
was reverted two days later.

The scheduler mirrors kubo's own policy: check hourly, GC only once the repo passes 90%
of Datastore.StorageMax, and use size-only repo stats so it does not walk the entire
flatfs blockstore. It is single-flighted, its timer is unref'd, and errors are contained
inside the tick so a rejected interval callback cannot take the daemon down. This also
covers a gap in pkc-js, which only GCs from a started local community's IPNS sync -- a
daemon that is up with no community started would otherwise never reclaim anything.

New flags: --enableIpfsGc / --no-enableIpfsGc (default on) and --ipfsGcIntervalMinutes
(default 60). Datastore.StorageMax is left at kubo's 10GB default.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Rinse12, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d094090-4643-48be-80be-d3e527f8cafc

📥 Commits

Reviewing files that changed from the base of the PR and between 6757a87 and d67d486.

📒 Files selected for processing (4)
  • README.md
  • src/cli/commands/daemon.ts
  • src/ipfs/repoGc.ts
  • test/kubo/repoGc.test.ts
📝 Walkthrough

Walkthrough

The daemon now runs optional, scheduled IPFS repository garbage collection through Kubo RPC. New flags control the feature and interval. The implementation applies storage watermarks, prevents overlapping runs, handles failures, and stops cleanly. Tests cover execution, scheduling, and RPC behavior.

Changes

IPFS repository garbage collection

Layer / File(s) Summary
Repository GC runtime
src/ipfs/repoGc.ts, test/kubo/repoGc.test.ts, package.json
Adds watermark-based Kubo RPC garbage collection, streamed result parsing, failure handling, scheduling, cancellation, and non-overlap protection. Updates @pkcprotocol/pkc-js and kubo.
Daemon GC integration and controls
src/cli/commands/daemon.ts, README.md
Adds GC enablement and interval flags. Starts the scheduler for the selected Kubo endpoint and stops it during daemon shutdown. Documents the flags.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: tomcasaburi

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The code shows the Kubo upgrade and RPC-based GC, but lockfile regeneration cannot be verified because package-lock.json was excluded. Inspect package-lock.json and confirm it records kubo 0.43.0 before merge; the file was excluded by the !**/package-lock.json filter.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the periodic IPFS GC feature and both dependency upgrades.
Out of Scope Changes check ✅ Passed The documentation, dependency updates, daemon flags, GC module, and tests all support the linked issue and stated PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch upgrade-kubo-0.43-repo-gc-119

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/commands/daemon.ts`:
- Around line 739-749: Add daemon-level tests covering the enableIpfsGc default,
disabling it with --no-enableIpfsGc, conversion of ipfsGcIntervalMinutes to
milliseconds, and invocation of the scheduler’s stop function during shutdown.
Exercise the daemon startup and lifecycle path containing startRepoGcScheduler,
while keeping direct scheduler behavior covered by repoGc.test.ts.

In `@src/ipfs/repoGc.ts`:
- Around line 70-188: Update the shared TypeScript configuration used by
config/tsconfig.json to set rootDir to ../src, ensuring the included source
files remain within the configured root. Add the missing `@tsconfig/node20` dev
dependency required by the base configuration, then verify npm run build and npm
run build:test complete successfully.
- Around line 42-50: Update readRepoStat to parse RepoSize and StorageMax from
body.SizeStat when present, falling back to body for compatibility, then apply
the existing numeric defaults. Update the repo/stat fixtures in repoGc tests to
return the documented nested SizeStat response shape.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 07a0384f-0374-4bcb-877a-738a3316589c

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb6ba9 and 6757a87.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • README.md
  • package.json
  • src/cli/commands/daemon.ts
  • src/ipfs/repoGc.ts
  • test/kubo/repoGc.test.ts

Comment on lines +739 to +749
// Runs against whichever kubo the daemon ends up talking to, including one started by
// another program (--pkcOptions.kuboRpcClientsOptions). pkc-js also GCs on the same
// watermark, but only from a started local community's IPNS sync — a daemon that is up
// with no community started would otherwise never reclaim anything (issue #119).
if (flags.enableIpfsGc)
stopRepoGcScheduler = startRepoGcScheduler({
kuboApiUrl: kuboRpcEndpoint,
intervalMs: flags.ipfsGcIntervalMinutes * 60 * 1000,
log: PKCLogger("bitsocial-cli:ipfs:repoGc")
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Add daemon-level coverage for the GC flags and scheduler lifecycle.

test/kubo/repoGc.test.ts tests the scheduler directly. Add a daemon test for the default enabled state, --no-enableIpfsGc, minute-to-millisecond conversion, and shutdown calling the stop function.

As per coding guidelines, “Add a test when you add a feature or fix a bug.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/commands/daemon.ts` around lines 739 - 749, Add daemon-level tests
covering the enableIpfsGc default, disabling it with --no-enableIpfsGc,
conversion of ipfsGcIntervalMinutes to milliseconds, and invocation of the
scheduler’s stop function during shutdown. Exercise the daemon startup and
lifecycle path containing startRepoGcScheduler, while keeping direct scheduler
behavior covered by repoGc.test.ts.

Source: Coding guidelines

Comment thread src/ipfs/repoGc.ts Outdated
Comment on lines +42 to +50
async function readRepoStat(
fetchImpl: FetchLike,
apiBase: string,
signal?: AbortSignal
): Promise<{ repoSize: number; storageMax: number }> {
const response = await postRpc(fetchImpl, apiBase, "repo/stat?size-only=true", signal);
const body = (await response.json()) as { RepoSize?: number; StorageMax?: number };
return { repoSize: Number(body.RepoSize ?? 0), storageMax: Number(body.StorageMax ?? 0) };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

: "${KUBO_RPC_URL:?Set KUBO_RPC_URL to the Kubo /api/v0 endpoint}"

curl -fsS -X POST "${KUBO_RPC_URL%/}/repo/stat?size-only=true" |
  jq -e '(.SizeStat // .) | has("RepoSize") and has("StorageMax")'

Repository: bitsocialnet/bitsocial-cli

Length of output: 238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate repoGc and tests =="
fd -a 'repoGc\.(ts|test\.ts)$' . || true

echo "== file outlines =="
for f in $(fd 'repoGc\.(ts|test\.ts)$' .); do
  echo "--- $f"
  wc -l "$f"
  ast-grep outline "$f" || true
done

echo "== relevant source =="
f="$(fd 'repoGc\.ts$' . | head -n 1 || true)"
if [ -n "${f:-}" ]; then
  cat -n "$f"
fi

echo "== relevant tests =="
tf="$(fd 'repoGc\.test\.ts$' . | head -n 1 || true)"
if [ -n "${tf:-}" ]; then
  cat -n "$tf"
fi

echo "== references to SizeStat/repo/stat =="
rg -n "SizeStat|repo/stat|readRepoStat|storageMax|repoSize" .

Repository: bitsocialnet/bitsocial-cli

Length of output: 29329


🌐 Web query:

Kubo API repo/stat response SizeStat RepoSize StorageMax

💡 Result:

The Kubo API endpoint repo/stat (and the corresponding stats/repo command) provides information regarding the local IPFS repository [1][2]. In the response structure, RepoSize and StorageMax are contained within a nested object called SizeStat [1][2]. The response format is as follows: - NumObjects (uint64): The number of objects currently stored in the repository [1][2]. - RepoPath (string): The file system path to the repository [1][2]. - SizeStat (object): A nested object containing size information [2]. - RepoSize (uint64): The size of the repository in bytes [3][2]. - StorageMax (uint64): The maximum storage limit for the repository in bytes, as defined in the configuration [4][5][2]. - Version (string): The version of the repository format [1][2]. Note that SizeStat is a grouping mechanism used to organize these specific storage metrics within the JSON response [1][2]. While repo stat is a common command-line interface command, stats/repo is the corresponding RPC API endpoint [2].

Citations:


Parse repo/stat from SizeStat.

Kubo documents RepoSize and StorageMax under SizeStat, but readRepoStat reads only top-level fields. With a real Kubo response, both become 0; the watermark check then runs when storageMax is 0, so GC skips the intended 90% threshold. Update readRepoStat to read body.SizeStat ?? body, and update the repo/stat stubs in test/kubo/repoGc.test.ts to match the nested shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ipfs/repoGc.ts` around lines 42 - 50, Update readRepoStat to parse
RepoSize and StorageMax from body.SizeStat when present, falling back to body
for compatibility, then apply the existing numeric defaults. Update the
repo/stat fixtures in repoGc tests to return the documented nested SizeStat
response shape.

Comment thread src/ipfs/repoGc.ts Outdated
Comment on lines +70 to +188
export async function runRepoGcIfDue(options: {
kuboApiUrl: URL | string;
log?: any;
force?: boolean;
signal?: AbortSignal;
fetchImpl?: FetchLike;
}): Promise<RepoGcOutcome> {
const log = options.log ?? PKCLogger("bitsocial-cli:ipfs:repoGc");
const fetchImpl = options.fetchImpl ?? (globalThis.fetch as unknown as FetchLike);
const apiBase = toConnectableApiBase(options.kuboApiUrl);

let repoSizeBefore: number | undefined;
let storageMax: number | undefined;

if (!options.force) {
let stat: { repoSize: number; storageMax: number };
try {
stat = await readRepoStat(fetchImpl, apiBase, options.signal);
} catch (error) {
// A daemon we can't stat is one to back off from, not to blindly GC.
log.error?.("Skipping repo gc: failed to read repo/stat from the kubo node", apiBase, error);
return { ran: false, skippedReason: "repo-stat-failed" };
}
repoSizeBefore = stat.repoSize;
storageMax = stat.storageMax;

// storageMax comes from Datastore.StorageMax. If the daemon reports no ceiling there is
// nothing to compare against, so fall back to GCing on the interval alone rather than
// never GCing at all.
if (stat.storageMax > 0) {
const threshold = stat.storageMax * GC_HIGH_WATERMARK;
if (stat.repoSize < threshold) {
log.trace?.(
`Skipping repo gc on ${apiBase} - repo size ${stat.repoSize} is below the ${GC_HIGH_WATERMARK * 100}% watermark ${threshold} of StorageMax ${stat.storageMax}`
);
return { ran: false, skippedReason: "below-watermark", repoSizeBefore, storageMax };
}
}
}

let reclaimedCids = 0;
try {
const response = await postRpc(fetchImpl, apiBase, "repo/gc?quiet=true", options.signal);
// repo/gc streams newline-delimited JSON, one object per reclaimed CID. Draining it fully
// is what makes this await mean "GC finished" rather than "GC started".
const text = await response.text();
for (const line of text.split("\n")) {
const trimmed = line.trim();
if (!trimmed) continue;
try {
const parsed = JSON.parse(trimmed) as { Key?: unknown; Error?: string };
if (parsed.Error) log.error?.("Failed to GC a block out of the ipfs repo", parsed.Error);
else if (parsed.Key) reclaimedCids++;
} catch {
// A malformed line is not worth aborting a completed GC over.
}
}
} catch (error) {
log.error?.("Failed to GC ipfs repo", apiBase, error);
return { ran: false, skippedReason: "gc-failed", repoSizeBefore, storageMax };
}

let repoSizeAfter: number | undefined;
try {
repoSizeAfter = (await readRepoStat(fetchImpl, apiBase, options.signal)).repoSize;
} catch (error) {
log.trace?.("repo gc finished but the follow-up repo/stat failed", error);
}

// How much a GC actually reclaims is worth logging rather than assuming: GC never touches
// pinned data, and a node with thousands of recursive pins can stay over the watermark.
log(
`GC reclaimed ${reclaimedCids} cids from the IPFS node ${apiBase} - repo size ${repoSizeBefore ?? "unknown"} -> ${repoSizeAfter ?? "unknown"}`
);
return { ran: true, reclaimedCids, repoSizeBefore, repoSizeAfter, storageMax };
}

/**
* Starts the periodic repo GC. Returns a stop function.
*
* The timer is unref'd so it never by itself keeps the daemon process alive, and runs are
* single-flighted: a GC that outlives its own interval must not have a second one stacked on top
* of it. Errors are swallowed inside the tick — an interval callback that rejects becomes an
* unhandledRejection and takes the daemon down (same failure mode as issue #37 bug 3).
*/
export function startRepoGcScheduler(options: {
kuboApiUrl: URL | string;
intervalMs?: number;
log?: any;
fetchImpl?: FetchLike;
}): () => void {
const log = options.log ?? PKCLogger("bitsocial-cli:ipfs:repoGc");
const intervalMs = options.intervalMs ?? DEFAULT_REPO_GC_INTERVAL_MS;
const abortController = new AbortController();
let inFlight: Promise<unknown> | undefined;

const tick = () => {
if (inFlight) return;
inFlight = runRepoGcIfDue({
kuboApiUrl: options.kuboApiUrl,
log,
signal: abortController.signal,
fetchImpl: options.fetchImpl
})
.catch((error) => log.error?.("repo gc tick error (will retry next interval)", error))
.finally(() => {
inFlight = undefined;
});
};

const timer = setInterval(tick, intervalMs);
timer.unref?.();
log(`Scheduled IPFS repo GC every ${intervalMs}ms against ${String(options.kuboApiUrl)}`);

return () => {
clearInterval(timer);
abortController.abort();
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
npm run build && npm run build:test

Repository: bitsocialnet/bitsocial-cli

Length of output: 7389


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- package scripts ---\n'
node -e "const p=require('./package.json'); console.log(JSON.stringify(p.scripts, null, 2))"

printf '\n--- tsconfig relevant settings ---\n'
cat -n config/tsconfig.json | sed -n '1,60p'

printf '\n--- node_modules availability ---\n'
test -d node_modules/typescript && echo "node_modules/typescript exists" || echo "node_modules/typescript missing"
test -d node_modules/@tsconfig/node20 && echo "node_modules/@tsconfig/node20 exists" || echo "node_modules/@tsconfig/node20 missing"

Repository: bitsocialnet/bitsocial-cli

Length of output: 255


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- package scripts ---'
node -e "const p=require('./package.json'); console.log(JSON.stringify(p.scripts, null, 2))"

printf '%s\n' ''
printf '%s\n' '--- tsconfig relevant settings ---'
cat -n config/tsconfig.json | sed -n '1,60p'

printf '%s\n' ''
printf '%s\n' '--- node_modules availability ---'
test -d node_modules/typescript && echo "node_modules/typescript exists" || echo "node_modules/typescript missing"
test -d node_modules/@tsconfig/node20 && echo "node_modules/@tsconfig/node20 exists" || echo "node_modules/@tsconfig/node20 missing"

Repository: bitsocialnet/bitsocial-cli

Length of output: 2468


🏁 Script executed:

#!/bin/bash
set -euo pipefail
npm run build && npm run build:test

Repository: bitsocialnet/bitsocial-cli

Length of output: 7389


🏁 Script executed:

#!/bin/bash
set -euo pipefail
npm run build && npm run build:test

Repository: bitsocialnet/bitsocial-cli

Length of output: 7389


Make the configured TypeScript builds pass.

npm run build && npm run build:test exits with TS6059 because config/tsconfig.json includes ../src/**/* while rootDir is defaulted to config via the absent @tsconfig/node20/tsconfig.json base. Set the common rootDir to ../src and include the missing dev dependency so the build can succeed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ipfs/repoGc.ts` around lines 70 - 188, Update the shared TypeScript
configuration used by config/tsconfig.json to set rootDir to ../src, ensuring
the included source files remain within the configured root. Add the missing
`@tsconfig/node20` dev dependency required by the base configuration, then verify
npm run build and npm run build:test complete successfully.

Source: Coding guidelines

Drops the repo/stat watermark check. GC only ever reclaims unpinned blocks, so kubo
already decides what is collectable -- reading Datastore.StorageMax to gate the call
just duplicated that decision on our side.

Removes both repo/stat calls (the pre-GC watermark check and the post-GC size probe),
GC_HIGH_WATERMARK and the `force` option. `runRepoGcIfDue` is now `runRepoGc`, which
POSTs repo/gc and counts reclaimed cids from the response stream.

Behaviour change: GC now runs on every tick regardless of repo size, where before it
was skipped below 90% of StorageMax.

Scheduling is unchanged -- hourly by default, single-flighted, unref'd timer, errors
contained inside the tick.
@Rinse12

Rinse12 commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

Simplified per review: dropped the Datastore.StorageMax watermark entirely. GC only ever reclaims unpinned blocks, so kubo already decides what is collectable — reading storage to gate the call just duplicated that decision on our side.

Removed both repo/stat calls (the pre-GC watermark check and the post-GC size probe), GC_HIGH_WATERMARK, and the force option. runRepoGcIfDue is now runRepoGc: it POSTs repo/gc?quiet=true and counts reclaimed cids from the response stream. The module is about half its previous size.

Behaviour change: GC now runs on every tick regardless of repo size, where before it was skipped below 90% of StorageMax. Worth being aware that this means a full pinset + blockstore walk every hour even on a nearly empty repo — that is the intended trade for not second-guessing kubo.

Scheduling is unchanged: hourly by default, single-flighted so a long GC never has a second stacked on it, unref'd timer, errors contained inside the tick.

Re-verified against a live kubo 0.43.0 node with the repo at ~0.01% of StorageMax — the case the old code would have skipped. It reclaimed 10 cids and shrank the repo 1,020,181 -> 19,641 bytes.

Tests updated: 9 unit tests (down from 12, since the watermark cases no longer exist), including a case asserting repo/stat is never called. Full suite green: 41 files, 319 passed, 1 skipped.

@Rinse12
Rinse12 merged commit 184aaff into master Aug 7, 2026
4 checks passed
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.

Bump kubo 0.42.0 -> 0.43.0 (blocks pkc-js#225 garbage collection)

1 participant